# Transcript Rehydration + `--conversation` Resume for Claude Code — Product Spec Linear: [REMOTE-1373](https://linear.app/warpdotdev/issue/REMOTE-1373) ## Summary Two capabilities for Claude Code cloud runs that together let a Claude session pick up where it left off: 1. **Transcript rehydration** (the foundation) — when a fresh Claude sandbox starts against an existing conversation, restore the prior transcript into `~/.claude/` on disk AND make Claude actually use it. The latter requires a Claude-specific server-side system-prompt body plus a user-turn preamble that overrides Claude's baked-in prompt, which previously caused resumed sessions to ignore saved state and effectively start over (including dropping any uncommitted workspace patches from cloud-to-cloud handoff). 2. **`--conversation ` for Claude Code** (the CLI surface) — lets a user deliberately trigger transcript rehydration against a finished Claude conversation, matching what `--conversation` does today for Oz. Works in both `run-cloud` and local `run`. Transcript rehydration is the more important of the two: it's what makes cloud-to-cloud handoff actually survive on Claude, and `--conversation` is just the user-facing way to invoke it on demand. ## Problem Cloud Claude runs upload their full state to GCS (`claude_code.json` transcript, `block_snapshot.json`, handoff workspace patches), but nothing on the read side picks it back up: - The stored transcript is never rewritten into Claude's on-disk layout, so `claude --resume ` finds nothing and starts fresh. - On cloud-to-cloud handoff, the existing Oz-style rehydration system prompt gets deprioritized relative to Claude's baked-in system prompt on resumed sessions, so Claude acknowledges the instructions and proceeds as if the workspace were reset — uncommitted changes are silently lost. - `--conversation` is Oz-only, so a finished Claude conversation is effectively read-only. ## Goals - **Transcript rehydration works on Claude** — whenever a Claude sandbox is spun up against an existing conversation (cloud-to-cloud handoff, or explicit `--conversation`), the prior transcript lands in `~/.claude/projects//.jsonl` with subagents, todos, and a `sessions-index.json` entry, AND any workspace patches from the prior sandbox are applied before Claude answers the new user turn. The "AND" is the hard part: it requires server-side prompt changes that survive Claude's own system prompt. - **`--conversation ` for Claude Code** — `warp agent run-cloud --harness claude --conversation --prompt "..."` spawns a new cloud run that resumes the prior Claude session; `warp agent run --harness claude --conversation --prompt "..."` does the same locally. - **Saves continue in place** — follow-up periodic/final saves write to the same server conversation id, same GCS objects, same Warp Drive object, same artifacts list. ## Non-goals - Transcript rehydration or resume support for third-party harnesses other than Claude Code. The abstraction is harness-agnostic (new CLIs add a `ResumePayload` variant + their own fetch override), but no other harness implements it today. - Preserving the envelope's original cwd — we rewrite it to the new run's cwd so `claude --resume` finds the jsonl. - Forking into a new conversation id, or a `--fork-session` branch. - UI changes to the AI Conversation viewer. ## User experience ### Transcript rehydration (automatic) Any Claude cloud run that resumes an existing conversation — whether via cloud-to-cloud handoff (REMOTE-1290) or via explicit `--conversation` — goes through the same rehydration path: 1. The sandbox CLI downloads the transcript envelope from `GET /harness-support/transcript`. The endpoint runs under the harness-support workload-token middleware and resolves the conversation server-side from the current task's `agent_conversation_id`, so the caller doesn't pass a conversation id. 2. Envelope's `cwd` is rewritten to the current working directory; main jsonl, subagent jsonls, per-agent todos, and a `sessions-index.json` entry are written under `~/.claude/projects//...`. 3. Claude is launched with `--resume ` so it picks up the on-disk transcript. 4. On every turn, `/harness-support/resolve-prompt` appends a third-party-CLI-strength rehydration body to Claude's system prompt and returns a one-line user-turn preamble in the new `resumption_prompt` response field. The Claude harness prepends that preamble to the user-turn prompt it pipes into the CLI, so Claude executes the pre-turn checklist (apply workspace patches, verify dirty files) before answering. Other harnesses can ignore `resumption_prompt`; old clients that don't know the field is there fall through to the unmodified prompt. 5. Periodic and final saves overwrite `/claude_code.json` and `/block_snapshot.json` in GCS so the AI Conversation viewer shows the merged state and artifacts stay attached. ### `--conversation ` invocation ``` warp agent run-cloud --harness claude --conversation --prompt "follow-up" warp agent run --harness claude --conversation --prompt "follow-up" ``` `--harness claude` is required when resuming a Claude conversation; the default `--harness oz` against a Claude id fails fast with an actionable error. The CLI never silently flips harness mid-flight because harness drives pre-load decisions (task config, CLI validation, server task creation). The client validates `--harness` against the conversation's stored harness before any task is created, then runs the same rehydration path described above. Local runs skip step 4 (the server's rehydration prompt is a no-op because there's no prior ended execution) but still rehydrate the transcript to disk so Claude's `/resume` picker sees it. ### Error and edge cases - Non-existent or inaccessible conversation id: fail fast, no task created. - Harness mismatch (either direction): fail fast with a message naming both sides, e.g. `conversation X was produced by the claude harness, but --harness oz was requested`. No task created. - Claude conversation with no stored transcript: `conversation has no stored transcript for the claude harness. The prior run may have crashed before saving any state`. - Transient transcript fetch failures: bounded exponential backoff inside `HarnessSupportClient::fetch_transcript`; permanent 4xx fails fast. - `claude --resume` failing at runtime (e.g. upstream session-index desync): surface the error and exit non-zero instead of silently starting a fresh session. - `--conversation` without a prompt/skill: same rejection as today's `has_prompt_source` check. - In-progress prior run: no special handling — same as Oz `--conversation`. ### Feature-flag gating - `FeatureFlag::CloudConversations` off → `--conversation` hidden and rejected (unchanged). - `FeatureFlag::AgentHarness` off → `--harness claude` rejected; the Claude transcript-rehydration client path is also gated on it. - `CloudToCloudHandoffEnabled` off → server returns the resolved prompt without the rehydration body or user-turn preamble (unchanged gate). ## Success criteria - **Transcript rehydration**: a Claude cloud run whose sandbox is replaced mid-run resumes with its prior transcript visible to Claude AND its uncommitted workspace patches applied before Claude answers the next turn — verified by the next turn's tool calls / git status, not just by the agent's acknowledgement text. - **Conversation resuming**: `run-cloud --conversation --prompt "..."` appends to the same AI Conversation in the UI and overwrites `/claude_code.json` / `block_snapshot.json` in place; `agent run --conversation ` locally grows `~/.claude/projects//.jsonl` with the prior entries before the new prompt runs. - New PR / plan / file artifacts from the resumed run attach to the same Warp Drive conversation object as the original. - Invalid inputs fail cleanly pre-launch with no side effects. ## Validation - **Transcript rehydration (cloud handoff)**: force a sandbox replacement on a Claude run with uncommitted changes; after handoff, confirm from the next-turn tool calls that `git apply` ran on the expected patches and that the files the patches touched are dirty. - **Conversation resuming (cloud)**: `run-cloud --harness claude --conversation ` with a follow-up; UI merges the conversation, prior uncommitted files reappear, saves overwrite in place. - **Conversation resuming (local)**: `agent run --harness claude --conversation ` locally; `~/.claude/projects//.jsonl` grows and Claude's `/resume` picker lists the session. - **Errors**: harness-mismatch id, missing-transcript id, non-existent id all fail cleanly pre-launch. - **Unit tests**: `--resume` vs `--session-id` flag selection, envelope cwd rewrite, session-index upsert, `HarnessSupportClient::fetch_transcript` retry behavior, harness-mismatch + resume-state-missing error paths. - **Integration tests**: `GET /harness-support/transcript` (Claude 307, Oz 400, no-conversation 400, unauthed 403); worker appends `--conversation` to CLI args when set. ## Resolved decisions - Transcript rehydration runs on any resumed Claude sandbox, not just `--conversation`; the same code path serves cloud-to-cloud handoff. - Third-party-CLI rehydration prompt is stronger than Oz's and is also echoed as a user-turn preamble — on resumed Claude sessions the system prompt alone gets treated as background instead of pre-turn action. - Same conversation id, in place (matches Oz `--conversation`). - Explicit `--harness claude` required; no auto-detect from metadata. - Reuse the envelope's Claude `session_id` so the transcript grows linearly instead of fragmenting across saves. - Rewrite `envelope.cwd` silently (cloud sandboxes routinely change cwd). - Rehydrate `~/.claude/sessions-index.json` alongside the jsonl so `--resume ` lookups succeed on recent Claude versions (upstream bugs claude-code#33912, #39667, #5768).