Public: anyone can read this; the crew can change it.

A conversation survives every release: /talk never answers 'No conversation found' #8754

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

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

opened this

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 with CLAUDE_CONFIG_DIR set. sessionsDir uses FORGE_CLAUDE_CONFIG_DIR if set, otherwise <dataDir>/claude-config, created with mode 0700. docs/APPS.md is updated to say so. Crew runs are unchanged: they still use FORGE_CLAUDE_CONFIG_DIR or Claude Code's own default.
  • Lost session: the failed attempt's text is dropped, and the turn restarts without --resume. withHistory gives 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.ts has a stub claude that 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 HOME are 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 claude process 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

Closes #8751

commented

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 with CLAUDE_CONFIG_DIR set. sessionsDir uses FORGE_CLAUDE_CONFIG_DIR if set, otherwise <dataDir>/claude-config, created with mode 0700. docs/APPS.md is updated to say so. Crew runs are unchanged: they still use FORGE_CLAUDE_CONFIG_DIR or Claude Code's own default.
  • Lost session: the failed attempt's text is dropped, and the turn restarts without --resume. withHistory gives 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.ts has a stub claude that 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 HOME are 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 claude process 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

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:

  1. 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.
  2. In the retry cleanup, also remove tool-role turns that carry the lost-session text (only agent turns are tracked in streamed), and when configured is given, chmod it too or document that its mode is the operator's.
commented

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.).

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs