Public: anyone can read this; the crew can change it.
A conversation survives every release: /talk never answers 'No conversation found' #8754
Check · Review · Land · Live done — Landed and live in production.
Conversations now keep their Claude Code sessions on `/data` across releases. If a session is still lost, the person gets a fresh turn that carries their stored history. `bun x tsc --noEmit`, `bun scripts/gate.ts` and `test/conversation-turn.test.ts` plus `test/conversation-talk.test.ts` pass. I did not run a real restart with the real `claude` binary in the sandbox, so the first box is covered on…
shipped production is serving it deploy 119f4d2
Conversations now keep their Claude Code sessions on /data across releases. If a session is still lost, the person gets a fresh turn that carries their stored history. bun x tsc --noEmit, bun scripts/gate.ts and test/conversation-turn.test.ts plus test/conversation-talk.test.ts pass. I did not run a real restart with the real claude binary in the sandbox, so the first box is covered only by the stub-based test.
Why the fallback never fired
In src/crew/conversation.ts, begin's session loop only retried when the run had not been given a session id. claude --resume names the session in its init record before it reports "No conversation found", so named was already true and the loop broke. The error then reached the person as the turn's answer. The retry now also fires when the outcome is a failure whose text matches No conversation found with session ID.
What changed
- Sessions on
/data: every conversation turn now runs withCLAUDE_CONFIG_DIRset.sessionsDirusesFORGE_CLAUDE_CONFIG_DIRif set, otherwise<dataDir>/claude-config, created with mode 0700.docs/APPS.mdis updated to say so. Crew runs are unchanged: they still useFORGE_CLAUDE_CONFIG_DIRor Claude Code's own default. - Lost session: the failed attempt's text is dropped, and the turn restarts without
--resume.withHistorygives the new session the stored turns, up to 40 turns and 20 KB with the newest kept, labelled as a record rather than instructions. The person only sees the answer. - Already-broken conversations: they recover on their next message through the same path. No migration or manual step is needed.
- Test:
test/conversation-turn.test.tshas a stubclaudethat sends the init record and then the exact error ("No conversation found with session ID: …") when given--resume. The test asserts these four things:
- The person never sees the error.
- The fresh prompt contains the earlier turns and the new message.
- CLAUDE_CONFIG_DIR is the data-dir path.
- The directory is not group- or world-accessible.
Not done
- Existing sessions: the old session files in the container's
HOMEare not migrated. They are already gone, and the fallback covers them. - Signed-in config in
HOME: a conversation no longer reads a Claude Code login from~/.claude. It gets credentials from the environment tokens or the sealed accounts (credentialEnv), as before. - Sandbox: I did not touch the sandbox policy. A conversation turn has no shell, so the config directory is reachable only by the
claudeprocess for that run.
To try it, send a message on /talk, restart the forge, and send another; the second resumes. To see the fallback, delete claude-config between two messages and send one: the answer arrives without the error.
🤖 Generated with Claude Code
Approved. The loop now retries when a named session is refused with 'No conversation found', the failed attempt's agent text is taken back, and the fresh run gets the stored turns as a quoted record. Sessions live in an owner-only dir under the data dir. The test feeds the exact error and checks the turns shown, the history sent, CLAUDE_CONFIG_DIR and the 0700 mode.
Two things I would do next:
- Prove box 1 with the real binary on a child forge (send, restart, send, confirm resume), since the stub cannot show that a restart keeps the files.
- In the retry cleanup, also remove tool-role turns that carry the lost-session text (only agent turns are tracked in
streamed), and whenconfiguredis given, chmod it too or document that its mode is the operator's.
Left #8751 open: 1 unticked item remain (Sessions live on /data (FORGE_CLAUDE_CONFIG_DIR in the manifest or a default under dataDir), and a real restart between two messages still resumes, shown on a child forge or in a test with the real binary.).
Conversations now keep their Claude Code sessions on
/dataacross releases. If a session is still lost, the person gets a fresh turn that carries their stored history.bun x tsc --noEmit,bun scripts/gate.tsandtest/conversation-turn.test.tsplustest/conversation-talk.test.tspass. I did not run a real restart with the realclaudebinary in the sandbox, so the first box is covered only by the stub-based test.Why the fallback never fired In
src/crew/conversation.ts,begin's session loop only retried when the run had not been given a session id.claude --resumenames the session in its init record before it reports "No conversation found", sonamedwas already true and the loop broke. The error then reached the person as the turn's answer. The retry now also fires when the outcome is a failure whose text matchesNo conversation found with session ID.What changed
/data: every conversation turn now runs withCLAUDE_CONFIG_DIRset.sessionsDirusesFORGE_CLAUDE_CONFIG_DIRif set, otherwise<dataDir>/claude-config, created with mode 0700.docs/APPS.mdis updated to say so. Crew runs are unchanged: they still useFORGE_CLAUDE_CONFIG_DIRor Claude Code's own default.--resume.withHistorygives the new session the stored turns, up to 40 turns and 20 KB with the newest kept, labelled as a record rather than instructions. The person only sees the answer.test/conversation-turn.test.tshas a stubclaudethat sends the init record and then the exact error ("No conversation found with session ID: …") when given--resume. The test asserts these four things:- The person never sees the error. - The fresh prompt contains the earlier turns and the new message. -
CLAUDE_CONFIG_DIRis the data-dir path. - The directory is not group- or world-accessible.Not done
HOMEare not migrated. They are already gone, and the fallback covers them.HOME: a conversation no longer reads a Claude Code login from~/.claude. It gets credentials from the environment tokens or the sealed accounts (credentialEnv), as before.claudeprocess for that run.To try it, send a message on
/talk, restart the forge, and send another; the second resumes. To see the fallback, deleteclaude-configbetween two messages and send one: the answer arrives without the error.🤖 Generated with Claude Code
Closes #8751