fix(server): report a failing end-of-stream terminator instead of swallowing it - #137
Conversation
…llowing it graph_build_run.py's finally block sends the single end-of-stream close() call inside `except Exception: pass`. Any failure there was completely invisible -- no log line, no counter -- which is exactly why #134's AttributeError on this same call (close() didn't exist after 45d4a80) survived a release unnoticed. #134 makes close() resolve again; this fixes the next failure mode, not just this one. The comment's reasoning was half right: a failure here must not mask the build outcome being unwound, so it is still never re-raised. But not-masking and not-reporting are different things -- the exception is now printed to stderr with its type and message before the lock releases. tests/test_graph_build_run_terminator.py forces run_build's first statement to fail (no store/DB dependency needed to reach `finally`), then covers both arms: close() raising is reported on stderr without re-raising and without leaking the lock, and the nominal path (close() succeeds) stays quiet. Verified failing against the pre-fix `except Exception: pass` handler. Fixes #135 Co-Authored-By: Claude <noreply@anthropic.com>
|
ZETETIC-REVIEW: APPROVE This is the defect that let the other one reach a release, and it is correctly treated as separate rather than folded into it. #136 makes The original comment's reasoning was half right, and the fix keeps the half that was: a failure here must not mask the build outcome being unwound. But not-masking and not-reporting are different things. The terminator now prints the exception type and message to stderr without re-raising, so the unwind is preserved and the silence is not. The consequence of the silence was real, not theoretical. Subscribers never received Both directions are tested, and the failing one was proven against the pre-fix code. One test forces
Merging. With #136 already landed, the patch release follows. |
…aces (#138) 3.1.0 could not build a graph at all: commit 45d4a80 deleted graph_event_stream's module-level emit/close/reset forwarders on the false premise of "no caller in this repository's history", killing every build with AttributeError on the first statement (#134, fixed by #136). #135/#137 stops the finally-block terminator from swallowing that same failure silently, which is why #134 reached a release unnoticed in the first place. #131 (mutation-test hardening, no behavior change) and #132 (automates RELEASING.md step 5, MCP Registry publish) also landed since 3.1.0 with no further breaking change, so this is a patch release: 3.1.0 -> 3.1.1, not a restatement of 3.1.0. Promotes CHANGELOG's Unreleased section (#132's entry) to 3.1.1, carries forward #134/#135's fix descriptions, and reopens an empty Unreleased. #131 gets no entry: it changes no shipped behavior. Aligns every version-bearing surface pyproject.toml/cortex_viz/identity.py/ server.json/.claude-plugin/plugin.json/.codex-plugin/plugin.json/ gemini-extension.json/.claude-plugin/marketplace.json/uv.lock (via `uv lock`) plus the two surfaces check_distribution_artifact does not cover, the README badge and docs/ROADMAP.md's "current version" line. Verified: `python -m scripts.check_distribution_artifact` passes against a built wheel; full suite 1317 passed/10 skipped; ruff check and format clean. Changelog-vs-tag audit: 3.1.0/2.8.0/2.7.1/2.7.0 all have matching tags, 3.0.0's "cut in the tree, never tagged or published" annotation is intact and unchanged, nothing new drifted. Co-authored-by: Claude <noreply@anthropic.com>
Summary
Fixes #135 — a separate defect from #134, and one that survives it.
graph_build_run.py'sfinallyblock sends the single end-of-streamclose()call insideexcept Exception: pass. Any failure there iscompletely invisible: no log line, no counter. That silence is exactly why
#134's
AttributeErroron this same call (close()didn't exist after45d4a80) survived a release unnoticed — #134 stops that failure, not the
next one this handler can still hide.
The existing comment's reasoning is half right: a failure here must not
mask the build outcome being unwound, so it must not be re-raised. But
not-masking and not-reporting are different things.
What changed
except Exception: pass→ the exception's type and message are printed tostderr before the lock releases. Still never re-raised.
Tests (
tests/test_graph_build_run_terminator.py)run_build's first statement is forced to fail (no store/DB dependencyneeded to reach
finally).close()raising is reported on stderr with the exception named, withoutre-raising and without leaking the build lock. Verified failing against
the pre-fix
except Exception: passhandler.close()succeeds) stays quiet — asserted, not assumed.Verification
uv run pytest tests/ -k "graph_build or graph_event or activity_stream or http_live"— 74 passed, 2 skipped.uv run ruff check/ruff format --check— clean.Co-Authored-By: Claude noreply@anthropic.com