Skip to content

feat(orchestration): import an existing Claude Code or Codex session by ID - #4617

Closed
Bil0000 wants to merge 13 commits into
pingdotgg:mainfrom
Bil0000:feat/import-session
Closed

feat(orchestration): import an existing Claude Code or Codex session by ID#4617
Bil0000 wants to merge 13 commits into
pingdotgg:mainfrom
Bil0000:feat/import-session

Conversation

@Bil0000

@Bil0000 Bil0000 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Why

T3 Code can only continue sessions it started. A Claude Code CLI session or a codex thread already on the machine has a transcript on disk and is fully resumable, but there is no way to open it here. Starting work in a terminal and wanting to finish it in the web UI means starting over.

What

Import session... in the command palette takes one session id.

The server reads where that session ran, creates the thread in the project covering that workspace, starts the provider session with a resume cursor, and copies the prior conversation into the transcript. The next turn continues inside the original provider session — the model still has its full context, because nothing is replayed to it.

  • The thread lands in the right project automatically. orchestration.resolveImportSession reads the session's working directory before anything is created. If a project already covers it, the thread goes there regardless of which project is open.
  • If no project covers it, the dialog says so and the button becomes Add project & import. Nothing is created without a second press.
  • /status is surfaced under the field as the fastest way to get a session id.

Claude Code and Codex only. No session picker, no listSessions browsing, no other providers.

How it fits together

Piece Change
Contracts thread.messages.import command and its thread.messages-imported event, plus the two RPC payloads
Decider command → event
Projection applied through the existing message projection — no new table, no migration
Server resolveImportSession (read scope) and importThread (operate scope)
Client resolveImportSession / importThread operations and the palette dialog

thread.messages.import is in the internal command union: only the server dispatches it, so a client cannot write a transcript.

Imported message ids are derived from the thread id, provider, zero-padded transcript index, and source message id — stable ordering, and a repeated import cannot duplicate rows.

A failure after the thread exists deletes that thread, and ThreadDeletionReactor stops the provider session behind it, so a failed import leaves nothing running and nothing in the sidebar.

Supporting changes

Two small provider changes were needed and are split into their own commits:

  • ProviderService gains readThread. The adapter registry is not visible past ProviderServiceLive, so an RPC handler had no way to read a provider thread snapshot.
  • The codex thread snapshot now carries the thread's workspace root and last activity time. thread/read already returned both and the adapter dropped them; without them, codex imports could not get the same workspace check as Claude. Codex reports those stamps in Unix seconds, so they are converted at the boundary and the field name carries the unit.

Commits

The branch is nine commits, each of which typechecks on its own:

  1. feat(provider): expose readThread on the ProviderService facade
  2. feat(provider): carry codex workspace root and last activity on thread snapshots
  3. feat(orchestration): add the thread.messages.import command and its event
  4. feat(orchestration): map provider transcripts to imported thread messages
  5. feat(orchestration): project imported thread messages
  6. fix(relay): do not publish agent awareness for an imported transcript
  7. feat(orchestration): resume an existing provider session into a new thread
  8. feat(client-runtime): add the session-import operations
  9. feat(web): add the Import session command palette action

Verification

Focused tests, per AGENTS.md:

vp test run apps/server/src/orchestration/ \
  apps/server/src/provider/Layers/CodexSessionRuntime.test.ts \
  apps/server/src/provider/Layers/CodexAdapter.test.ts \
  apps/server/src/relay/AgentAwarenessRelay.test.ts \
  apps/server/src/server.test.ts \
  apps/web/src/components/CommandPalette.logic.test.ts \
  packages/client-runtime/src/state/threadReducer.test.ts

Test Files  25 passed (25)
     Tests  410 passed (410)

Typecheck and format clean across contracts, client-runtime, server, web and mobile.

Manually, against a real Claude Code session:

  1. claude -p "Remember this fact for later: the import canary code is FERRET-4412. Reply with exactly: OK" inside a project directory.
  2. Copy the id (/status, or from ~/.claude/projects/<project>/).
  3. From a different project, run Import session... and paste it.
  4. The thread appears under the project the session actually ran in, with both prior messages rendered.
  5. Ask "What is the import canary code?" — the resumed session answers FERRET-4412. The canary only exists in the on-disk transcript, so this confirms the real session resumed rather than the copied messages being replayed.
  6. Repeat with a folder that is not a project yet: the dialog reports it and offers Add project & import.
  7. Repeat with a bogus UUID: an actionable error, and no thread is left behind.

Not covered

Codex was not exercised at runtime — no codex CLI on the machine used. Its transcript mapper, workspace check and timestamp conversion are unit-tested; the live resume path is not.

Video

CleanShot.2026-07-27.at.11.24.49.mp4

Note

Add import of existing Claude Code or Codex sessions by ID into new threads

  • Adds resolveImportSession and importThread RPC endpoints on the WebSocket layer, allowing clients to look up an external session and import its transcript into a new orchestration thread.
  • New importThread.ts factory orchestrates the full flow: validates provider support (claudeAgent/codex), creates a thread, converts the external transcript via importedMessages.ts, dispatches thread.messages.import and thread.session.set, and cleans up via thread.delete on failure.
  • Adds a thread.messages-imported event type through the full stack: contracts, decider, projector, projection pipeline, and client-side reducer, with deduplication by messageId.
  • Adds an ImportSessionDialog UI component in the web app, accessible via a new "Import session..." action in the command palette, which guides the user through provider selection, session ID input, optional project creation, and navigation to the imported thread.
  • Risk: if the import flow is interrupted after thread creation but before cleanup completes, a partial thread may remain.

Macroscope summarized db5946c.

Bil0000 added 9 commits July 27, 2026 02:29
Transports resolve provider adapters through ProviderService; the adapter
registry is not visible past ProviderServiceLive. Reading a provider thread
snapshot from an RPC handler needs a facade method, mirroring how
rollbackConversation routes.
…d snapshots

thread/read already returns the thread's cwd and its recency stamp; the
adapter dropped both. Callers that need to know where a codex thread ran, or
when it was last active, had nothing to read.

Codex reports those stamps in Unix seconds, so parseThreadSnapshot converts
them and the field name carries the unit.
…vent

Adds the command, the thread.messages-imported event, and the payloads for
orchestration.resolveImportSession and orchestration.importThread, plus the
decider case that turns one into the other.

thread.messages.import stays in the internal command union: only the server
dispatches it, and a client must not be able to write a transcript.
…ages

Pure mappers from a Claude session transcript or a codex thread snapshot to
ThreadImportedMessage. Non-user/assistant entries and empty text are skipped.

Message ids are derived from the thread id, provider, transcript index and
source message id, so a repeated import cannot duplicate rows. The index is
zero-padded because the message projection breaks ties on message id, and an
unpadded index would order a ten-message transcript 1, 10, 2.
Applies thread.messages-imported through the existing message projection. No
new table, no migration.

The command read model folds the same event so the decider's message-based
invariants see an imported transcript, matching how every other event that
changes thread messages is registered.
Copying a transcript into a thread is not agent activity. Every sibling event
that adds message content is already excluded; thread.messages-imported fell
through to the default and queued a spurious alert.
…hread

resolveImportSession reads where a Claude session ran and whether a project
already covers that workspace, so a caller can offer to add the missing
project before anything is created.

importThread creates the thread in that project, starts the provider session
with a resume cursor, copies the transcript in and binds the session. Both
drivers refuse a session that ran in another workspace. A failure after the
thread exists deletes it, and ThreadDeletionReactor stops the provider
session behind that.

resolveImportSession takes the orchestration read scope; importThread takes
the operate scope.
resolveImportSession and importThread operations plus their atom commands,
serialised per session id so the same import cannot run twice concurrently.

The thread reducer applies thread.messages-imported so an open thread shows
the transcript without waiting for a fresh snapshot.
Opens a dialog that takes one session id, offers Claude Code and Codex, and
labels the field Session ID or Thread ID to match.

On submit it resolves where the session ran. If a project already covers that
workspace the thread lands there; if none does, the dialog says so and the
button becomes Add project & import, so nothing is created without a second
press.
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f31aa5e6-4472-4e7b-ae0f-82abb58d09db

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jul 27, 2026
Comment thread apps/web/src/components/CommandPalette.tsx
Comment thread apps/web/src/components/ImportSessionDialog.tsx
Comment thread apps/server/src/orchestration/importThread.ts Outdated
Comment thread apps/web/src/components/CommandPalette.logic.ts

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect Service Conventions — 1 finding.

The importFailure helper in apps/server/src/orchestration/importThread.ts is a pure error-construction mapper, which the conventions ask to avoid. See the inline comment.

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/orchestration/importThread.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a complete new feature for importing Claude Code and Codex sessions, including new RPC endpoints, orchestration events, external SDK integration, and a new UI dialog workflow. New features with this scope of cross-cutting changes and external integrations warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Bil0000 added 4 commits July 27, 2026 08:43
deriveProviderInstanceEntries preserves server ordering, where a configured
custom instance can precede the synthesised default. Taking the first ready
entry meant the generic Claude Code / Codex option silently resolved through
a custom instance and its model configuration.
The global keydown listener toggled the command palette regardless of the
import dialog, stacking two modals with competing focus handling.
A project created for the session stayed invisible to the retry path, so
pressing Import again built a second project for the same workspace and hit
the duplicate-workspace invariant.
…rupted

tapError never runs on fiber interruption, so a client disconnecting mid
import left the thread in the sidebar and its provider session running.
onExit compensates on any non-success exit.

Error construction moves to each failure boundary, per the service
conventions, so the message and cause stay visible where they originate.
@Bil0000

Bil0000 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

All five review findings addressed, one commit each, pushed as db5946cee.

Finding Commit
High — import option bound to a custom instance instead of the default 581459f89
Palette shortcut stacked a second modal over the import dialog fcb9ee59c
Retry after a created project built a duplicate and hit the workspace invariant b5250c8f9
tapError skipped compensation on interruption db5946cee
importFailure relocated error construction away from the boundary db5946cee

Each was reproduced before being changed. The interruption one has a regression test that forks the import, interrupts it during startSession, and asserts the compensating thread.delete — it fails against the previous implementation.

Test Files  23 passed (23)
     Tests  364 passed (364)

Typecheck, lint and format clean across contracts, client-runtime, server, web and mobile.

@IARI

IARI commented Jul 28, 2026

Copy link
Copy Markdown

I want to be able to look up stuff from old conversations with a decent UI.
Thanks a lot for this PR - I would absolutey love it to get merged.
There are many reasons for me to not want to use claude code and its annoying terminal UI.
The main reason for me is, that it is completely impossible to get a decent overview over current/previous conversations.

@Bil0000

Bil0000 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

I want to be able to look up stuff from old conversations with a decent UI.

Thanks a lot for this PR - I would absolutey love it to get merged.

There are many reasons for me to not want to use claude code and its annoying terminal UI.

The main reason for me is, that it is completely impossible to get a decent overview over current/previous conversations.

Happy to hear that man

Just didnt get what you mean, can you plz briefly explain exactly what you want?

I will make sure to add your request if its smth useful :)

@IARI

IARI commented Jul 29, 2026

Copy link
Copy Markdown

@Bil0000 Apologies, with my previous comment I did not mean to suggest that something about the PR needs to change for my usecase.

I was merely venting about how the TUI in cladue code is not good to navigate conversations, Codex or T3-Code are much better at that.
This ist the main reason I want to move my old Claude conversations/threads to T3 code, and why I like your PR (in the meantime until this gets merged, i have just created a small python script for myself)

If I were to suggest anything to add at all:
I have not checked out your branch and tested it, but If I understand it correctly, your PR adds a command that allows to import conversations into an existing thread.

I would consider it useful, if there was any way

  • to create a thread from scratch that corresponds to a claude code conversation (maybe that is already effectively possible if your import is the first command which also creates the thread?)
  • a way to batch-import multiple conversations as new/corresponding threads

While not strictly necessary, I could see that this would be a nice addition to make t3-code adoption a bit easier

@juliusmarminge

Copy link
Copy Markdown
Member

Closing in favor of #2829 (orchestration V2).

#2829 deletes the V1 orchestration layer this PR builds on — apps/server/src/orchestration/**, provider/Layers/*Adapter.ts and provider/Services/** are removed and replaced by apps/server/src/orchestration-v2/**, with the IPC surface renamed to ORCHESTRATION_V2_WS_METHODS. The files this PR touches either no longer exist or are rewritten, so it can't be rebased — it would need reimplementing against the V2 adapters.

This is not a judgement on the change itself. Several of these are real gaps we still want fixed; the base just moved out from under them.

Once #2829 merges, please rebase onto main, port the change to the V2 equivalent, and reopen (or open a fresh PR). Ping me and I'll prioritise the review.

@Bil0000

Bil0000 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@juliusmarminge reimplemented against the V2 orchestrator as #5499, targeting the #2829 branch so it can rebase trivially once that merges. Same UX as this PR plus two-way transcript sync (T3 turns land back in the provider's own session file, and turns made in the CLI after import sync into the thread). Verified live against a real Claude Code session — details in the PR description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants