/goal activation record is written mid-turn, orphaning an in-flight advisor call and permanently bricking the session
/goal activation record is written mid-turn, orphaning an in-flight advisor call and permanently bricking the session
Summary
Setting a session goal (/goal) while Claude is working writes the Stop-hook
activation notice into the transcript immediately, at submission time, rather than
at a turn boundary. If an advisor server-tool call is in flight, thatisMeta: true user-role record lands between the assistant's server_tool_use
and its advisor_tool_result, splitting one assistant turn into two so the result
no longer immediately follows its call.
Ordinary queued input does not do this — it correctly waits for the turn boundary
(control case below). The defect is specific to the goal-activation record's
injection path.
The corruption is latent. It does not fail at the time it is written — roughly a
thousand ordinary turns ran fine afterwards in both of my cases. The first failure
coincides with the first background task-notification, hours later, and from that
point the session is permanently dead for ordinary turns too:
API Error: 400 messages.N.content.0: unexpected `tool_use_id` found in
`advisor_tool_result` blocks: srvtoolu_XXXX. Each `advisor_tool_result` block
must have a corresponding `server_tool_use` block before it.
The session is permanently unresumable — --resume replays the same bad prefix
and fails identically. From the user's side it looks like the model is stuck in an
error loop: every turn, including plain text turns, returns the same 400.
Impact
Total loss of conversational context for the affected session. In my case ~2.5 hours
of a long-running task. Because the failure surfaces hours after the action that
caused it, and is first triggered by background work rather than by anything the user
typed, there is no visible link between cause and symptom. Committed work on disk
was fine, but nothing in the session could be continued, and the only obvious remedy
(/clear) "fixes" it precisely by discarding the context you are trying to save.
Evidence
Two independent occurrences on this machine, same signature, different versions.
Line numbers are records in the session .jsonl.
2026-08-24, v2.1.241:
| line | type | role | content | timestamp |
|-----|------|------|---------|-----------|
| 159 | assistant | assistant | server_tool_use (advisor) srvtoolu_01Y5c… | 10:24:45.708Z |
| 160 | attachment | — | — | 10:27:26.450Z |
| 161 | user | user | "A session-scoped Stop hook is now active with condition: …" | 10:27:26.451Z |
| 162 | assistant | assistant | advisor_tool_result srvtoolu_01Y5c… | 10:28:03.567Z |
The session's first user message was at 10:20:58; the goal was submitted at
10:27:26, during the opening task. Its queue-operation enqueue record carries the
identical millisecond timestamp (10:27:26.451) as the transcript record above —
i.e. it was written on submission, not on delivery, and landed inside an advisor call
that ran 10:24:45 → 10:28:03.
2026-08-15, v2.1.232 — identical shape at lines 60–63. First user message
12:34:16Z; goal submitted 12:35:28Z, i.e. 72 seconds into the session, landing
between the advisor call at 12:35:09Z and its result at 12:37:06Z.
Control case — same session, same in-flight call. An ordinary queued user
message ("dosbox is free until further notice") was submitted at 12:36:26Z, also
during that advisor call, and was correctly held until 12:37:09Z — after the
result at 12:37:06Z. So normal queued input respects the turn boundary and the
goal-activation record does not. This rules out "any queued input can interleave"
as the explanation.
In both files the parentUuid chain confirms it is a genuine interleave rather than
a display artifact: the advisor_tool_result's parent is the user record, not theserver_tool_use.
Why it surfaces hours later
The orphan does not break the session when it is written. Both sessions kept working
normally for a long time afterwards:
| session | orphan written | first 400 | latency | trigger of first 400 |
|---------|----------------|-----------|---------|----------------------|
| 08-24, v2.1.241 | 10:27:26Z | 12:55:41Z | 2h 28m | <task-notification> (background command finished) |
| 08-15, v2.1.232 | 12:35:28Z | 14:07:55Z | 1h 32m | <task-notification> (background command finished) |
In the 08-24 session, all five <task-notification> turns failed and no ordinary
turn ever did (the last ordinary turn, 12:53:36Z, succeeded, withmessageCount: 1023 and no compaction record anywhere in the file). In the 08-15
session both task-notification turns failed, and after that point ordinary turns
failed too — "are you still there?" at 22:13Z, "please continue" the next morning.
So the session is not merely degraded during background work; it is permanently
unusable from the first task-notification onward.
Open question — the reduced message list. The failing requests reportmessages.3.content.0 (08-24) and messages.5.content.0 (08-15), while those
sessions contained ~1023 and ~1300 messages respectively. So whatever request fails
is a small rebuilt message list, not the live conversation, and the orphaned pair
appears near its start. I could not determine from the transcripts what assembles
that list, why it tolerates the orphan for hours beforehand, or why the failure then
persists for ordinary turns — no request bodies are logged. Stating the measurement
and leaving the mechanism open.
Reproduction
- Start a session with a task that puts Claude to work immediately.
- While it is working — and while an
advisorserver-tool call is in flight — set a
session goal (/goal <condition>). This is the ordinary usage pattern: kick off
the task, then set the goal condition.
- Inspect the transcript: the goal record now sits between the
server_tool_useand
its advisor_tool_result. Ordinary turns keep working at this point.
- Later, let a background task complete so a
<task-notification>re-invokes the
session. That turn 400s, and the session is unusable from then on.
Note both of my occurrences arose this way; I have not deliberately re-provoked it,
so the timing window above is inferred from the two transcripts rather than tested.
Step 4 is what makes the bug visible, and it is why the failure appears hours after
the action that caused it — which makes the two very hard to connect from the user
side.
Expected: the goal activation record is held to the next turn boundary, as ordinary
queued input already is (or the request builder tolerates the split).
Actual: 400 on this and every subsequent turn, forever.
Suggested fixes
- Route the goal-activation record through the same deferral the normal input queue
already uses, so it cannot be written while a server-tool call is pending.
- And/or make the request builder resilient: reorder or drop an orphaned
advisor_tool_result when assembling the request, instead of emitting an invalid
message list.
A guard at load time would also convert a permanent brick into a recoverable state.
Workaround
Repairable offline by moving the interloping records to after the advisor result and
rewriting both the parentUuid chain and the physical line order. I verified the
repaired session against a checker that reproduces the exact 400 message on the
unrepaired file, so the check can actually fail.
Environment
- Claude Code 2.1.241 (also reproduced on 2.1.232)
- Linux 7.1.8-200.fc44.x86_64 (Fedora 44), bash
- Long-running session (~2.7 MB transcript),
--dangerously-skip-permissions