Adversarial review: the T023 signal was only tested against a stub graph - #271
Merged
Merged
Conversation
Every sweep test uses a StubGraph returning a plain dict, so nothing exercised the step that actually matters: whether `live_tool_failed` survives LangGraph's state merge between the node that sets it and the `ainvoke` the sweep reads. Checked on the real graph with a tool patched to raise. It survives -- `live_tool_failed=True` for the live question, absent for ordinary retrieval -- and the answer text was "I could not find the specific species represented in Reactome", which is precisely the prose that used to trigger a retry. The signal is what separates them now, on the real path rather than a stand-in. The review's actual finding is an omission that is correct and looked like an oversight. The no-tools fallback does not set the flag, and it should not: `get_mcp_tools` remembers a failed start, so every later call returns None too and a retry could never succeed. Marking it transient would buy the sweep a second attempt guaranteed to fail. It is a persistent condition and the gate should fail loudly on it. Now said where the code is, and pinned by a test, because the next person to notice the gap will otherwise close it. One test of mine was wrong rather than the code: it asserted the fallback leaves the selection at `None`, when the fallback sets `[]` explicitly. Both mean every collection, so the test was asserting a representation instead of the meaning. It now asserts what `resolve_collections` makes of it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Follow-up review of #270, which had sabotage verification but no dedicated pass.
The gap
Every sweep test uses a
StubGraphreturning a plain dict, so nothing exercised the step that actually matters: whetherlive_tool_failedsurvives LangGraph's state merge between the node that sets it and theainvokethe sweep reads.Checked on the real graph with a tool patched to raise:
It survives. And the live answer is exactly the prose that used to trigger a retry — so the signal is what separates them, demonstrated on the real path rather than a stand-in.
The finding
An omission that is correct and reads like an oversight: the no-tools fallback does not set the flag, and should not.
get_mcp_toolsremembers a failed start, so every later call returnsNonetoo and a retry could never succeed — marking it transient would buy the sweep a second attempt guaranteed to fail. It is a persistent condition and the gate should fail loudly (Principle IV).Now stated where the code is and pinned by a test, because the next person to notice the gap will otherwise close it.
One of my tests was wrong, not the code
It asserted the fallback leaves the selection at
None, when the fallback sets[]explicitly. Both mean every collection, so it was asserting a representation rather than the meaning. It now asserts whatresolve_collectionsmakes of it.571 passed, 1 skipped; mypy over 148 files, ruff clean.
🤖 Generated with Claude Code