anthropics / anthropics/claude-code
A `/goal` Stop-hook record written mid-turn splits an advisor server-tool call from its result, permanently bricking the session (400 loop)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 147k
- Forks
- 24k
- PR merge metrics
- PR metrics pending
Description
Version: 2.1.277 (Claude Code)
Platform: Linux 6.8.0 / bash
Model: claude-opus-5 (1M context)
Session: 468afc33-879a-43de-8605-ce59e6436546
Summary
Setting a session-scoped goal (/goal, a Stop hook) while a turn is in
flight causes the isMeta: true notice record to be appended to the
transcript in the middle of an assistant message that has an
outstanding advisor server-tool call. The single assistant message is
then persisted as two runs of records separated by a user record.
On a later request the API rejects the rebuilt history:
API Error: 400 messages.5.content.0: unexpected `tool_use_id` found in
`advisor_tool_result` blocks: srvtoolu_01XJ8SNvoZBHgPB8mgjgt5Cv. Each
`advisor_tool_result` block must have a corresponding `server_tool_use`
block before it.
Every subsequent request fails identically. The session cannot be
resumed, compacted, or continued — /compact re-sends the same history
and fails the same way. The only recoveries are /rewind (lossy) or
hand-editing the transcript JSONL.
Evidence
All records below share assistant message id msg_011Cf9b2qQDjRFtbabVkdtgS
except the two injected ones. Line numbers are from the session JSONL.
| line | timestamp (UTC) | type | content |
|---|---|---|---|
| 21862 | 19:46:28.459 | assistant | thinking |
| 21863 | 19:46:28.481 | assistant | server_tool_use: srvtoolu_01XJ8SNvoZBHgPB8mgjgt5Cv (name advisor) |
| 21864 | 19:46:35.947 | attachment | — |
| 21865 | 19:46:35.948 | user, isMeta: true |
"A session-scoped Stop hook is now active with condition: …" |
| 21866 | 19:47:58.854 | assistant | advisor_tool_result: srvtoolu_01XJ8SNvoZBHgPB8mgjgt5Cv |
| 21867 | 19:47:58.856 | assistant | thinking |
The advisor call took ~90s (19:46:28 → 19:47:58). The /goal record was
written 7.5s into it, i.e. squarely inside the in-flight turn.
This is the only defect of its kind in the session: 20 server_tool_use
blocks (all advisor) and 20 advisor_tool_result blocks; the other 19
pairs are contiguous within a single assistant message and are fine.
Delayed onset. The session kept working for ~52 minutes after the
split was written, then began failing at 20:40:08 and never recovered —
12 consecutive identical 400s through 22:00:09, when it was abandoned
(session cost at that point: $93). There was no compaction in that
window (last compact_boundary was 19:45:14, i.e. before the split),
so the in-memory history was evidently fine and something later rebuilt
the request from a form where the pair straddled a boundary.
The reported index messages.5 is consistent with a short prefix (the
19:45 compaction summary is messages[0]): the intervening user record
gives the prefix trimmer a legal cut point between the call and its
result, which no other advisor pair in the session has. Once the trim
reached that point, the server_tool_use half was dropped and the
result became content.0 of the next assistant message.
Repro (hypothesized)
- Start a turn that makes a long-running
advisor(server-tool) call. - While it is in flight, issue
/goal <condition>. - Observe that the Stop-hook notice is appended to the transcript
between theserver_tool_useandadvisor_tool_resultrecords. - Continue the session until the prefix trim reaches that boundary.
Expected
Either the meta record is buffered until the assistant turn closes, or
the request builder keeps a server-tool call and its result in one
assistant message regardless of interleaved meta records.
Actual
The turn is persisted split; the history becomes unserializable; the
session is unrecoverable in-product.
Suggested fixes (any one prevents the brick)
- Don't write meta/system-notice records into an open assistant
turn. Queue them until the turn boundary. This is the root fix — the
transcript should never represent one assistant message as two runs
separated by a user record. - Merge on rebuild. When reconstructing the API payload, coalesce
adjacent assistant records sharing amessage.idacross intervening
meta-only user records (which are not sent to the API anyway). - Treat a server-tool call + result as atomic in the prefix trimmer.
Never choose a cut point between aserver_tool_useand its matching
result block. - Fail soft. On a 400 of this shape, self-heal (drop the orphaned
result, or the pair) rather than looping the same rejected request 12
times. At minimum, surface actionable guidance instead of a bare 400 —
nothing in the product tells the user the session is permanently dead
or how to recover it.
Workaround
With the session exited, delete the injected attachment + meta-user
records from the transcript and re-point the advisor_tool_result
record's parentUuid at the server_tool_use record's uuid, making
the assistant message contiguous again. Then claude --resume <id>.
This recovered the session with no loss of conversation.
Caveat for anyone copying this: address the records by uuid, not by
line number. A line-numbered sed -i that runs twice deletes the next
two records instead (in our case the advisor_tool_result and a
thinking block), leaving the mirror-image corruption.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
No source files or tests are named. Start by reproducing the /goal Stop-hook scenario, then trace transcript JSONL writes, assistant-message reconstruction, and prefix trimming around the server_tool_use and result. Done means an in-flight advisor call remains atomic and resumed sessions produce valid API history without a 400 loop.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100