feat(orchestration-v2): import an existing Claude Code or Codex session by id, with two-way transcript sync - #5499
Conversation
…vider_import history origin - ORCHESTRATION_V2_WS_METHODS.resolveImportSession / .importSession with input/output schemas and OrchestrationV2ImportSessionError - historyOrigin gains "provider_import" for threads whose history was copied from an external provider session
…V2 thread SessionImportService resolves a session id to its workspace/project and imports its transcript as a new thread: - The transcript is read before anything is written (Claude via the agent SDK's getSessionInfo/getSessionMessages, Codex from the rollout file under $CODEX_HOME/sessions), so a failed import leaves nothing behind. - History lands as synthetic message.updated/turn-item.updated pairs with deterministic ids, mirroring the legacy v1 transcript importer; the thread carries historyOrigin "provider_import" so runless items render. - A provider thread is synthesized with nativeThreadRef = the external session id (strength strong) and, for Claude, nativeConversationHeadRef = the last transcript message, so the first turn takes the adapter's resume path and every T3 turn writes back into the provider's own transcript. - ensureSynced re-reads the transcript on thread reads and appends turns made outside T3 (guarded by mtime, event-id/native-id/text dedupe, and skipped while a run is active), tracked in orchestration_v2_session_imports (migration 046). - Duplicate imports of the same external session are refused.
resolveImportSession/importSession RPC wrappers plus command atoms with serial concurrency keyed by (environment, external id).
Palette action opens ImportSessionDialog: provider segmented buttons (ready Claude Code/Codex instances only), session id input, two-phase resolve with "Add project & import" when the session's workspace is not a project yet, and navigation to the imported thread.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Effect service conventions review of the new SessionImportService and its contract error. Four convention violations found; details inline.
Posted via Macroscope — Effect Service Conventions
ApprovabilityVerdict: Needs human review This PR introduces a significant new feature: importing existing Claude Code or Codex sessions with two-way transcript synchronization. It adds new UI components, RPC endpoints, a database migration, and complex orchestration logic for reading and syncing external provider transcripts. New features of this scope and complexity warrant human review. You can customize Macroscope's approvability policy. Learn more. |
- Imported transcripts now keep tool activity: Claude thinking blocks become reasoning items, Bash calls become command_execution items paired with their tool results, Edit/Write calls become file_change items, and other tools fall back to a compact command rendering. The same applies to Codex rollouts (reasoning summaries and function calls paired by call id). Only plain-text rows become conversation messages; ids stay compatible with previously imported threads. - Transcript entries synced after the import take position ordinals after the thread's current maximum instead of the import's 0-band, so turns made in the CLI after T3 turns render chronologically instead of inside the history block. - Codex rollout discovery resolves the instance's configured home from its continuation key (codex:home:<path>) before falling back to $CODEX_HOME / ~/.codex. - Sync failures now log a pretty-printed cause.
|
Pushed
All verified live against real Claude Code sessions (tool-use session import renders the executed command with output; a CLI turn added after a native T3 turn appears at the bottom of the thread). 10 server tests + 6 palette tests pass; typecheck/lint/fmt clean. The one remaining limit is Codex live resume, which needs a machine with Codex auth — covered by unit/integration tests up to the |
Correctness (Macroscope review): - Import write phase (positions, events, imports row) now runs in one transaction under the per-thread lock ensureSynced uses, so a failure can no longer leave a thread partially imported, and the negative sync cache can no longer race the import and permanently disable syncing (the import clears the cache entry under the same lock). - Fork inheritance gates provider-imported runless items by position ordinal, so forking from an earlier run no longer copies transcript entries synced after that run. - Codex rollout lookup matches the full `-<id>.jsonl` segment (a short id can no longer collide with another rollout's tail), stats before reading (an append between read and stat can no longer be skipped by the next sync), and only treats a missing sessions directory as not-found — other filesystem errors surface as read failures. - The role+text sync dedupe backstop is a multiset: each T3 run message absorbs one transcript echo, so a genuinely repeated prompt in the CLI still imports. - The web dialog deletes the project it created when the import half of "Add project & import" fails. Conventions: - OrchestrationV2ImportSessionError now carries a structured reason discriminator and the externalId; errors are constructed at each failure boundary instead of through a helper. - SessionImportService declares its interface inline in Context.Service and acquires FileSystem/Path from the environment in make. CI: migration contiguity test now expects 46 entries (046 added).
The active-run guard read the projection before the transcript read and several queries, so a run dispatched in that window could interleave with the sync write. The run-state check now repeats inside the write transaction; on an active run the sync aborts without touching the imports row, so the next thread read retries.
A tool call and its result are separate transcript lines, so a sync could import the command before its output existed and the deterministic event id then kept the completed output out forever. Sync now re-emits imported command items whose output has since appeared — same item id and ordinal, one deterministic revision event — and the projector upserts them in place. Covered by an integration-test scenario.
Reimplements #4617 against the orchestration V2 runtime, as requested in #4617 (comment) — plus a safe two-way sync between the imported thread and the provider's own on-disk transcript.
Why
T3 Code can only continue sessions it started. A Claude Code CLI session or a
codexthread already on the machine is fully resumable, but there was no way to open it here. The V1 attempt (#4617) was closed because #2829 replaced the orchestration layer it was built on; this is the V2 reimplementation, targeting the #2829 branch.What
Import session...in the command palette takes one session id.orchestration.resolveImportSessionreads the session's working directory and title before anything is created. If a project covers that directory, the thread lands there regardless of which project is open; if not, the dialog relabels toAdd project & importand nothing is created without a second press. Unlike V1, Codex sessions resolve too (workspace + title come from the rollout file).getSessionInfo/getSessionMessages), Codex from its rollout.jsonlunder$CODEX_HOME/sessions(no live app-server needed). History is appended as syntheticmessage.updated/turn-item.updatedpairs with deterministic ids — the same mechanism as the legacy V1 thread importer — under a newhistoryOrigin: "provider_import". There is no create-then-compensate dance: a failed import leaves nothing behind.nativeThreadRefis the external session id (strength: "strong"; for Claude,nativeConversationHeadRefis set to the last transcript message so the adapter takes theresumepath instead of colliding onsessionId).ProviderTurnStartServicethen resumes natively — the model keeps its full context; nothing is replayed.Two-way sync
~/.claude/projects/**/<id>.jsonl, and Codex to its own rollout.ensureSyncedre-reads the transcript (throttled by source mtime, serialized per thread, skipped while a run is active) and appends turns made outside T3. Idempotency and safety come from three dedupe layers: deterministic event ids, provider-native item ids already present as turn items, and exact role+text match against native run messages — so T3's own turns echoed back by the provider are never re-imported. Sync state lives inorchestration_v2_session_imports(migration 046).Verification
CommandPalette.logictests (17/17 in the file).fmt,lint,typecheckclean across contracts, server, client-runtime, web (pre-existing@cursor/sdk-related failures unaffected).claude -p "Remember this fact for later: the import canary code is FERRET-4413. Reply with exactly: OK"in a project directory.Import session...→ paste id → thread appears with both prior messages rendered.~/.claude/projects/**/<id>.jsonl(T3 → provider).claude -p --resume <id> "…second canary is BADGER-7788…"in the terminal, reopen the thread in T3 — the CLI turn appears (provider → T3), and T3's own turns are not duplicated.Demo
https://drive.google.com/file/d/16rBmArbhFZyHfzLWxNY7pxJNyqt7D9hs/view?usp=sharing
Known limits
Only plain user/assistant text is importedSolved: tool activity now survives the import — Claude thinking blocks become reasoning items, Bash calls become command items paired with their results, Edit/Write calls become file-change items (other tools get a compact command rendering); same for Codex rollouts (reasoning summaries, function calls paired by call id). Verified live against a real session with tool use.Turns synced from the CLI render above native T3 runsSolved: sync-appended entries take position ordinals after the thread's current maximum, so CLI turns made after T3 turns render chronologically. Verified live (CLI turn after a native T3 turn lands at the bottom).Solved: rollout discovery resolves the codex instance's configured home from its resolved home layout first, then$CODEX_HOMEonly$CODEX_HOME, then~/.codex.thread/resumeadapter path.Note
Add import and two-way sync of Claude Code and Codex sessions into orchestration-v2 threads
SessionImportService) that reads an external Claude Code or Codex session, maps its transcript to deterministicOrchestrationV2events, and stores it as a new thread withhistoryOrigin: 'provider_import'.ensureSynced) that detects provider-side conversation added after import, backfills command outputs, and is called automatically ongetThreadProjectionandsubscribeThread.resolveImportSession,importSession) with auth scopes and client-runtime command functions, plus anImportSessionDialogUI accessible from the command palette.ProjectionStoreordinal logic so that provider-imported runless items are only included in history up to the correct run band when forking.orchestration_v2_session_importstracking table with a unique index on(driver, external_id).getThreadProjectionandsubscribeThreadnow perform a file-system or network read (ensureSynced) on every call for imported threads; failures are swallowed with a warning but add latency.Macroscope summarized d265cee.