Skip to content

Adversarial review: the T023 signal was only tested against a stub graph - #271

Merged
adamjohnwright merged 1 commit into
mainfrom
010-t023-review
Sep 20, 2026
Merged

adamjohnwright merged 1 commit into
mainfrom
010-t023-review

Conversation

@adamjohnwright

Copy link
Copy Markdown
Contributor

Follow-up review of #270, which had sabotage verification but no dedicated pass.

The gap

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:

live, tool raises    live_tool_failed=True   answer="I could not find the specific species represented in React..."
ordinary retrieval   live_tool_failed=None   answer="In the context of Alzheimer's disease, CDK5, particularly..."

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_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 (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 what resolve_collections makes of it.

571 passed, 1 skipped; mypy over 148 files, ruff clean.

🤖 Generated with Claude Code

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>
@adamjohnwright
adamjohnwright merged commit c3894a9 into main Sep 20, 2026
10 checks passed
@adamjohnwright
adamjohnwright deleted the 010-t023-review branch September 20, 2026 01:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant