chore(release): 0.14.2 - three runtime/transport hotfixes#79
Merged
Conversation
Three independent fixes that fell out of the 0.14.1 demo run, plus a stub refresh on the two _RecordingRuntime mocks whose shape the new @Protect emit broke. * fix(decorators): @Protect now emits a tools/track_tool event after the wrapped body returns. Pre-0.14.2 the protected decorator only fired the gate check and skipped the bookkeeping emit, so the dashboard never saw a protect execution. The emit goes through the same sink as llm_call events so it picks up the dedup LRU at runtime.track() for free. * fix(runtime): track_tool event carries tokens: 0 and a fresh uuidv7 execution_id. The backend's SdkTrackRequest requires both fields as non-Optional u64 / string; pre-0.14.2 the event dict only carried type / tool_name / is_retry and the deserializer rejected it. Span lifecycle events get the same tokens: 0 default via _enrich_event. * fix(transport): approval-resolved WS callback is now a plain sync function. The WebSocket dispatch path invokes it as a dict -> None callable; the previous async-decorated coroutine was silently dropped, so the sync threading.Event inside _wait_for_approval_resolution never got set on the first approval round-trip - the demo's first approval hung forever. Caught 2026-07-24. * fix(runtime): treat websocket cancellation as a clean shutdown signal. WebSocketConnection.close() cancels the receive task to unblock the waiter during normal end of session; on Python 3.11+ CancelledError derives from BaseException, so the old except Exception branch re-raised it and produced a noisy debug line on every clean exit. The new except CancelledError branch is silent and the finally cleanup still runs. * test: refresh _RecordingRuntime in tests/test_protect.py and tests/test_preflight_fail_policy.py with a track_tool stub. The previous shape only mocked track_event, which is why the @Protect emit silently failed under the new decorator wiring. * chore: ruff format on the three source files touched by this release (decorators / runtime / transport). The format-only reformat of the 65 unrelated files is intentionally deferred to a separate PR. * docs: 0.14.2 changelog entry describing the four fixes end-to-end. No SDK_MIN_VERSION bump. No public API change. No on-wire breaking change. Backends on 1.0.0 keep working unchanged. Verification: - pytest -n auto -> 1369 passed, 7 skipped, 29 warnings. - ruff check src/ tests/ -> All checks passed. - mypy src/nullrun --strict -> Success: no issues found in 36 source files.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
0.14.2 patch release
Three independent fixes that fell out of the 0.14.1 demo run, plus a stub refresh on the two
_RecordingRuntimemocks whose shape the new@protectemit broke.Fixed
@protectdecorator now emits atools/track_toolevent -decorators.py:470anddecorators.py:521(sync + async wrappers) callruntime.track_tool(fn.__name__, metadata={"arguments": _safe_kwargs(kwargs)})after the wrapped body returns. Pre-0.14.2 the protected decorator only fired the gate check and skipped the bookkeeping emit, so the dashboard never saw aprotectexecution. The new emit goes through the same sink asllm_callevents so it picks up the dedup LRU atruntime.track()for free.track_toolevent carriestokens: 0and a freshuuidv7execution_id-runtime.py:3077stamps both fields onto everytool_callevent. The backend'sSdkTrackRequestrequirestokens: u64(non-Optional) and a threadableexecution_id; pre-0.14.2 the event dict only carriedtype/tool_name/is_retryand the deserializer rejected it. Span lifecycle events (span_start/span_end) get the sametokens: 0default viaruntime.py:2161.transport.py:1757wrapped_approval_resolvedwas previously declaredasync defto be awaitable, but the WebSocket dispatch path invokes it as a plain function (the dispatch signature isdict[str, Any] -> None, not awaitable). The async-decorated coroutine was silently dropped, so the syncthreading.Eventinsideruntime._wait_for_approval_resolutionnever got set on the first approval round-trip - the demo's first approval hung forever. Caught 2026-07-24.runtime.py:1160now catchesasyncio.CancelledErrorbefore the genericexcept Exceptionblock.WebSocketConnection.close()cancels the receive task to unblock this waiter during normal shutdown; on Python 3.11+CancelledErrorderives fromBaseException(notException), so the old code re-raised it and produced a noisyWS receive loop ended: <never logged>debug line on every clean shutdown. The new branch is silent and the path stays contained.Tests
tests/test_approval_ws_sync_callback.py- 103 lines of new coverage for the WS approval-resolved dispatch path: the callback is invoked as a sync function, thethreading.Eventis set, the wait returns within the timeout, and the previous async-decorated shape is asserted-not-present.tests/test_runtime_branches.py- 36 lines of new coverage for theawait conn._receive_taskcancellation path:CancelledErroris no longer re-raised out of the block as aWS receive loop ended: ...debug line, and thefinallycleanup still runs.tests/test_protect.pyandtests/test_preflight_fail_policy.py-_RecordingRuntimestubs refreshed with atrack_toolmock. The previous shape only mockedtrack_event, which is why the new@protectemit silently failed under the new decorator wiring.Compatibility
tokens: 0/execution_idfields ontrack_toolevents are forwarded exactly as minted; the backend'sSdkTrackRequestalready accepts them (the 0.14.0 envelope contract).def on_approval_resolved(payload: dict) -> None); only the in-transport wrapper changed fromasync deftodef.except Exceptionwas silent; the only user-visible delta is a removed debug log line on clean shutdown.Verification
pytest -n auto --cov=src/nullrun --cov-branch --cov-report=xml --cov-fail-under=0-> 1369 passed, 7 skipped, 29 warnings in 36.77s, cov 81.49%.ruff check src/ tests/-> All checks passed.mypy src/nullrun --strict-> Success: no issues found in 36 source files.Out of scope
ruff format --checkis intentionally deferred to a separate PR. This release only formats the three source files touched by the fix series (decorators / runtime / transport).