Conversation
This was referenced Sep 18, 2026
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.
Closes #237.
A Fable 5.1 turn dies with a synthetic 400 carrying
Missing or invalid internal Fable 5.1 effort anchorwhenever the model makes a tool call. Seven occurrences across three sessions in a week, in four message variants.Root cause
Not what #237 guessed. That issue blamed boundaries carrying more than one user message. The plan lookup was never the problem — OpenCode keeps
chat.headers.message.idbound to the originating user message throughout a tool loop, so the tracker resolves correctly on every continuation.The anchor is pushed onto the user message that is current when the messages hook runs. Provider lowering then appends
assistant tool_use→user tool_resultafter it, because a tool result is a user-role message in Anthropic's wire format. Validation required the anchor to sit on the last user message:Reproduced values at the throw:
Everything correlated. Only the position differed, and it differed for a reason the check did not model.
Fix
An anchor may precede trailing user messages only when every one of them is a non-empty
tool_result-only message whosetool_use_idappears in the immediately preceding assistant'stool_useblocks.Still fail-closed, unchanged: plain user suffixes, holes, reorderings, mutations, scope mismatch, digest mismatch, anchor-token mismatch, multiple anchors.
Diagnosability
Equally important, and the reason this took two commits. The two throw sites emitted the identical string, and the throw happens before the dump path, so a production occurrence left zero trace in the log — all seven had to be reconstructed from the host database.
anchor_placementandanchor_token, with distinct messageseffortMarkerFailureResponselogs at warn with check code, anchor and planned boundary IDs, both indexes, anchors found, marker count, scopes, and match stateVerification
Reproduction:
accepts authenticated effort history on a tool continuation— pre-fix0 pass, 1 failwithanchor_placement.The second commit exists because the first one's safety half was untested. Mutating the whole
tool_resultpairing predicate toreturn truepassed the entire suite at 1897/0 — the existing rejection test buildsuser · user(anchor) · user(plain)with no assistant between, so it bails at an earlier guard and never reaches the pairing. The uncovered shape is an ordinary new user turn after an assistant reply.Three tests now pin it, each independently mutation-proven:
rejects a plain user turn after an assistant replyreturn truerejects a tool result that does not match the preceding tool usetoolUseIds.has(block.tool_use_id)rejects a tool-result user message mixed with text.every(...)→.some(...)Re-ran the original surviving mutation against the final tree: now 3 fail where it previously passed clean.
Gates: core 199/0 · opencode 1900/0 · pi 114/0 · typecheck clean.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Fixes Fable 5.1 tool calls dying with a 400 error when the assistant makes a tool call. The anchor placement check was too strict — it required the effort anchor to sit on the last user message, but Anthropic's wire format appends tool results as user-role messages after it.
Bug Fixes
tool_result-only message whosetool_use_idmatches atool_useblock in the preceding assistant message.tool_resultcontent, scope mismatches, and anchor token mismatches still fail closed.Diagnosability
anchor_placementandanchor_tokencheck codes with distinct messages.Closes #237.
Written for commit 3251e64. Summary will update on new commits.