bb 0.37.0 · provider claude-code · Claude Code CLI 2.1.226 · macOS 26.5.2.
Also seen on 0.36.0 and 0.35.1 / CLI 2.1.225. Source references are against
b5d89f3.
The CLI probe below was run on 2.1.229.
What I was trying to do
Send /clear to a thread and have the thread go back to idle afterwards, so a
script could clear a worker thread and carry on.
What happens
The clear itself succeeds, but the thread never leaves active. It sits there
indefinitely, showing as working when nothing is running. The turn produces no
turn/started and no turn/completed:
11 turn/completed <- the previous, ordinary turn
12 client/turn/requested {"source":"tell","input":[{"type":"text","text":"/clear"}]}
13 provider/unhandled {"rawType":"sdk/conversation_reset", …}
14 thread/identity {"providerThreadId":"3886608c-…"}
<- nothing further; status stays "active"
It settles only when something else closes the turn. An ordinary later turn
does it. So does any unrelated interrupt, which is the part that makes this hard
to see: whether one arrives is chance.
What I expected
status back to idle once the CLI reports the run finished — and it does
report it. Driving the CLI directly shows the whole sequence:
echo '{"type":"user","message":{"role":"user","content":"/clear"}}' \
| claude --print --input-format stream-json --output-format stream-json --verbose
conversation_reset {}
system {"subtype":"init"}
result {"subtype":"success","num_turns":0,"is_error":false,"result":""}
It is a zero-work run — the command resolves before any model call — but the
provider still closes it.
Impact
bb thread wait --status idle never returns, so any script that clears a thread
and waits will hang. It exits 2 with Error: Timed out waiting for thread <id> to reach status idle. if you bound it, and blocks for the default 1200s if you do
not. Anything keyed on the idle transition does not fire for these turns either.
It also costs an interactive user the whole thread, under a message that names
the wrong cause. One of my threads held a turn open for 14h 26m after a
/clear, and the next event was not a settle:
2206 Aug 11 17:35:43 client/turn/requested {"input":[{"text":"/clear"}]}
2207 Aug 11 17:35:44 provider/unhandled {"rawType":"sdk/conversation_reset", …}
2208 Aug 11 17:35:44 thread/identity {"providerThreadId":"c7ccfbd2-…"}
<- nothing for 14h 26m
2209 Aug 12 08:02:10 system/error {"code":"thread_command_failed",
"message":"Thread interrupted because
the host daemon disconnected",
"detail":"Please retry the thread to continue."}
2210 Aug 12 08:02:10 system/thread/interrupted {"reason":"host-daemon-restarted"}
Three things that adds:
- The error names the wrong cause. The daemon restart did not break the
thread. It is what finally closed the turn the /clear left open 14 hours
earlier, and it reported itself as the cause. A user reading "the host daemon
disconnected" goes looking at their network or their machine sleeping. I only
found the real cause by reading the event log.
- A queued message never drains. I typed
hi five seconds after the clear.
bb queued it correctly, behind a turn it believed was running. Queued messages
drain on turn completion, and this turn ended as an interrupt, so the queue
held. bb thread queue delete was the way out — findable only once you know
the queue is what holds it.
- It survived an upgrade. That clear was on 0.36.0. bb replaced itself with
0.37.0 six hours before the daemon restart and the open turn came through
untouched.
The same thread settled an identical /clear four days earlier, but only by
luck: a trailing interrupt from a stop I had already requested happened to land
one second later. That thread has now taken three clears, across 0.35.1, 0.36.0
and 0.37.0, and settled none of them itself.
Why it happens
That result reaches bb and is dropped.
packages/agent-runtime/src/claude-code/translate-message.ts gates the whole
result handler on there already being a turn to complete:
case "result": {
// …
if (state.currentTurnId) {
// …
events.push({ type: "turn/completed", /* … */ });
args.turnState.finishTurn({ state, threadId: stateKey });
}
break;
}
currentTurnId is set by ensureTurnStarted, which the adapter calls when it
translates an assistant message, a stream event, a tool call, or a compaction
status. A /clear produces none of those, so the one message that would have
closed the turn takes the empty branch.
From there the domain behaves exactly as written. In
packages/domain/src/thread-lifecycle.ts, active has three exits:
active: {
"run.succeeded": "idle",
"run.failed": "error",
"stop.requested": "stopping",
},
turn/completed is what becomes run.succeeded
(apps/server/src/internal/turn-completed-events.ts). None was emitted, so
there is no transition to take, and the table's own note applies: "Absent cell
= the event is a no-op in that status." Nothing reconciles it later either —
system/provider-turn-watchdog is marked "Legacy persisted watchdog
diagnostic; retained for read/decode/render only, with no current producer"
(packages/domain/src/thread-events.ts).
The same missing onTurnStart explains the queue symptom, since accepted input
is drained there. The thread above carries four turn/input/accepted events and
not one for either of its clears.
Reproduction
bb thread spawn --project <id> --model claude-haiku-4-5-20251001 \
--prompt "Reply with only READY"
bb thread wait <thread-id> --status idle
bb thread tell <thread-id> "/clear"
bb thread wait <thread-id> --status idle --timeout 60
# -> times out; `bb thread show <thread-id>` reports "active" indefinitely
bb thread tell <thread-id> "hello" # any ordinary turn settles it
bb thread stop <thread-id> # or this: same effect, no model call
I also have a scripted case that asserts the absence of turn/started and
turn/completed rather than just the status, and bounds its wait so it fails
fast against a bb that has fixed this. It lives in a plugin of mine that is not
public yet; happy to share it if that is useful.
A fix
A PR follows this issue. It settles the turn the way the synthetic no-response
message already does one case earlier in the same file, gated on
pendingAcceptedUserMessages so that a result whose turn has already closed
still emits nothing. @bb/agent-runtime is green at 918 tests on b5d89f3, and
the new test fails without the change.
bb 0.37.0 · provider
claude-code· Claude Code CLI 2.1.226 · macOS 26.5.2.Also seen on 0.36.0 and 0.35.1 / CLI 2.1.225. Source references are against
b5d89f3.The CLI probe below was run on 2.1.229.
What I was trying to do
Send
/clearto a thread and have the thread go back to idle afterwards, so ascript could clear a worker thread and carry on.
What happens
The clear itself succeeds, but the thread never leaves
active. It sits thereindefinitely, showing as working when nothing is running. The turn produces no
turn/startedand noturn/completed:It settles only when something else closes the turn. An ordinary later turn
does it. So does any unrelated interrupt, which is the part that makes this hard
to see: whether one arrives is chance.
What I expected
statusback toidleonce the CLI reports the run finished — and it doesreport it. Driving the CLI directly shows the whole sequence:
It is a zero-work run — the command resolves before any model call — but the
provider still closes it.
Impact
bb thread wait --status idlenever returns, so any script that clears a threadand waits will hang. It exits 2 with
Error: Timed out waiting for thread <id> to reach status idle.if you bound it, and blocks for the default 1200s if you donot. Anything keyed on the idle transition does not fire for these turns either.
It also costs an interactive user the whole thread, under a message that names
the wrong cause. One of my threads held a turn open for 14h 26m after a
/clear, and the next event was not a settle:Three things that adds:
thread. It is what finally closed the turn the
/clearleft open 14 hoursearlier, and it reported itself as the cause. A user reading "the host daemon
disconnected" goes looking at their network or their machine sleeping. I only
found the real cause by reading the event log.
hifive seconds after the clear.bb queued it correctly, behind a turn it believed was running. Queued messages
drain on turn completion, and this turn ended as an interrupt, so the queue
held.
bb thread queue deletewas the way out — findable only once you knowthe queue is what holds it.
0.37.0 six hours before the daemon restart and the open turn came through
untouched.
The same thread settled an identical
/clearfour days earlier, but only byluck: a trailing interrupt from a stop I had already requested happened to land
one second later. That thread has now taken three clears, across 0.35.1, 0.36.0
and 0.37.0, and settled none of them itself.
Why it happens
That
resultreaches bb and is dropped.packages/agent-runtime/src/claude-code/translate-message.tsgates the wholeresulthandler on there already being a turn to complete:currentTurnIdis set byensureTurnStarted, which the adapter calls when ittranslates an assistant message, a stream event, a tool call, or a compaction
status. A
/clearproduces none of those, so the one message that would haveclosed the turn takes the empty branch.
From there the domain behaves exactly as written. In
packages/domain/src/thread-lifecycle.ts,activehas three exits:turn/completedis what becomesrun.succeeded(
apps/server/src/internal/turn-completed-events.ts). None was emitted, sothere is no transition to take, and the table's own note applies: "Absent cell
= the event is a no-op in that status." Nothing reconciles it later either —
system/provider-turn-watchdogis marked "Legacy persisted watchdogdiagnostic; retained for read/decode/render only, with no current producer"
(
packages/domain/src/thread-events.ts).The same missing
onTurnStartexplains the queue symptom, since accepted inputis drained there. The thread above carries four
turn/input/acceptedevents andnot one for either of its clears.
Reproduction
I also have a scripted case that asserts the absence of
turn/startedandturn/completedrather than just the status, and bounds its wait so it failsfast against a bb that has fixed this. It lives in a plugin of mine that is not
public yet; happy to share it if that is useful.
A fix
A PR follows this issue. It settles the turn the way the synthetic no-response
message already does one case earlier in the same file, gated on
pendingAcceptedUserMessagesso that a result whose turn has already closedstill emits nothing.
@bb/agent-runtimeis green at 918 tests onb5d89f3, andthe new test fails without the change.