Repository navigation
fix(supervisor): resume a conversation only once Claude Code has it on disk - #67
Merged
Merged
Conversation
…n disk
A bot restarted before its first turn (a mode, model or settings change
right after it was created) came back with `--resume` for a session Claude
Code had never saved. Claude Code answered "No conversation found", the chat
got a failed turn nobody asked for, and the bot lost a few seconds before
the fresh start.
Seen with Claude Code 2.1.284 (spec 19): the conversation is written when
its first turn begins. A process killed at the first `system/init` cannot be
resumed; one killed at the replay of the message can. So the session id is
now remembered from the `user` replay instead of `system/init`, and never at
launch: until then a restart uses `--session-id` with a new id.
When Claude Code does not have the conversation it was told to resume, its
`result` (no turn before it, `error_during_execution`, "No conversation
found" in `errors`) is no longer a turn in the chat. It marks the next start
as a new conversation, however long the process takes to exit.
A conversation a new one takes the place of also leaves `bots.session_id`,
so a daemon restart does not resume it: before, `bots.restart {fresh}`
followed by a daemon restart went back to the old conversation.
Spec 7.3, 7.4, 8.1 and 19 say the new rule.
Merged
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.
The problem
A bot restarted before its first turn came back with
--resumefor a conversation Claude Code had never saved. Seen on 2026-09-30 with a dev daemon and Claude Code 2.1.284:bots.create, thenbots.setPermissionMode20 ms later.--resume <session id>and Claude Code answeredNo conversation found with session ID: <uuid>;turnitem witherror: "error_during_execution"before the owner had sent anything;The same happens to any bot whose mode or model is changed before its first message, since the daemon remembered the session id at launch.
What Claude Code does (2.1.284, recorded in spec 19)
--resumeof a session it does not have: the stderr line above, then on stdout, with nosystem/initbefore it, aresultwithsubtype: "error_during_execution",is_error: true,num_turns: 0,duration_ms: 0,total_cost_usd: 0,errors: ["No conversation found with session ID: <uuid>"]and noterminal_reason. The process exits by itself with code 1.--resumeafterwardssystem/inituserreplay (0.6 to 1.4 s after the init)assistantresultSo
system/initis too early to call a session resumable. The replay of the turn's message is the first event that is late enough.The fix
userreplay of a turn, no longer fromsystem/init, and never at launch. Until a conversation has had a turn, a restart starts again with--session-idand a new id.resultis recognised by its subtype and the start of the error text. It is not a turn, so it adds no chat item and closes no pending turn, and it marks the next start as a new conversation however long the process takes to exit. If the text ever changes, the daemon falls back to what it did before (a failed turn, then a fresh start if the process dies withinfresh_start_if_dies_within_s).bots.session_idtoo. Before,bots.restart {fresh: true}followed by a daemon restart went back to the old conversation, because the database still had its id until the new one had a turn.crates/botloftd/src/supervisor/session.rsandcrates/botloftd/src/chat/session.rsare new;events.rsgets a little shorter.Tests
crates/botloftd/tests/sessions.rs, on FakeRuntime. All four failed before the fix:system/init;fresh_start_if_dies_within_sto exit;Four older tests that expected
--resumefor a bot that never had a turn now give the bot a turn first.cargo fmt --all --check,cargo clippy --workspace --all-targets -- -D warningsandcargo test --workspacepass. The app is not touched.With the real Claude Code
Done with a dev daemon on a scratch home and Claude Code 2.1.284 (Haiku):
bots.create, thenbots.setPermissionMode20 ms later: the log shows the secondbot startedwithresumed=false, no stderr line, and the chat stays empty.resumed=true, and the bot repeated the word from its earlier turn.%USERPROFILE%\.claude\projects\..., daemon started:No conversation foundin the debug log, no new item in the chat, the nextbot startedwithresumed=false, andbots.session_idempty.Merging with #63
This PR is on
main. A trial merge withfeat/bot-effort-context(#63, and #64 and #66 on top of it) has one conflict and two tests to adjust, whichever merges second:crates/botloftd/src/supervisor/spawn.rs: keep both sides, without thesessions.insert(bot.id.clone(), launch.session.clone());line of feat(daemon): effort per bot, the context in use and compacting the conversation #63.tests/bot_effort.rs,an_idle_bot_restarts_on_its_new_effort_in_the_same_conversation:let session = s.turn(&first).await;instead of reading--session-id.tests/compaction.rs,a_process_that_ends_takes_its_compaction_and_a_new_conversation_its_size:s.turn(&first).await;before the firstholds(...).With those three edits the merged tree passes
cargo test -p botloftd. The other open PRs merge cleanly with this one.