Repository navigation
Claude Code transcripts cannot be imported as Maka Sessions #3142
Description
Problem
ExternalSessionAdapter is source-agnostic and Codex uses it today, but there is no adapter for Claude Code. A user with history in ~/.claude/projects/<mangled-cwd>/<uuid>.jsonl has no way to bring it into Maka.
Part of #2499. An earlier attempt is at #2954 (closed); this issue records the problem and what that attempt established, so the next one does not rediscover it.
What the transcript format requires
- One model response is split across several records sharing
message.id, and the records of one response can be separated by atool_result. Folding only consecutive records imports one answer as several rows — 383 occurrences across 33 of 1236 local transcripts, 378 of them continuations carrying onlytool_use. - Tool results arrive as
type: 'user'records. They are the harness replying, not the human, and must not import as user Turns. - Sub-agent transcripts are whole
isSidechainfiles, not interleaved records (verified: 0 mixed across 1231 files), so exclusion is per-file. - The directory name mangles path separators, so only a record's own
cwdcan answer a project-scoped query. isMetarecords are context Claude Code injected on the user's behalf and were never typed by a human.
The load-bearing constraint
This is the part worth knowing before starting.
isTrustworthyRecoveredTerminal (runtime-ledger-repair.ts) discards a reconstructed terminal RuntimeEvent unless a recorded turn_state corroborates it. Every imported Run is then repaired to failed/missing_terminal_event, and the whole conversation renders as errors.
A transcript does not record that a Turn ended — only that another one began. So an importer that wants its history to render has to assert a recorded terminal it never observed. That assertion is wrong in a way that matters: 50 of 376 real root transcripts end with a prompt that has no answer after it (a killed process, a crash, or a Session still running), and each would import as a Turn that finished normally.
The honest shape is two changes that only work together:
- Adapters emit
turn_stateonly for facts the transcript carries — the interrupt notice andisApiErrorMessage— and letderiveTurnRecordslabel everything elseinferred. - The Ledger accepts an inferred terminal on the transcript-materialization path, where
transcriptRunHeaderderives the header from the same messages, so there is no independent record for the transcript to contradict.
Note that (2) needs both isTrustworthyRecoveredTerminal and terminalStatus in runtime-event-backfill.ts to open — the latter also refuses to emit a terminal event without a turn_state, so relaxing only the first leaves the Run at failed.
Since this touches Runtime terminal-state authority, it may be worth landing as its own change before the adapter, rather than inside it.
Suggested scope
- The adapter, registered alongside Codex.
- Fixtures for both the 2.1 and 2.0 transcript shapes — 2.0 predates
ai-title/last-prompt, never emits thinking blocks, and puts text andtool_usein one record where 2.1 splits them. - Reuse
claudeUserMessageTextand the interrupt literal from@maka/core/foreign-sessionrather than re-implementing them; the scanner and the importer deciding "what did the user say" differently would be a real bug.
Activity
Joob1n commentedon Aug 17, 2026
Claiming this one.
Planning to sequence it as two changes rather than one, since the second only makes sense once the first has landed:
isTrustworthyRecoveredTerminalandterminalStatusaccept an inferred terminal on the transcript-materialization path. That is where the pressure to forge arecordedstatus comes from, and it touches Runtime terminal-state authority, so it deserves review on its own rather than buried inside an adapter.turn_stateonly for the interrupt notice andisApiErrorMessage, with fixtures for both the 2.1 and 2.0 transcript shapes.Happy to reorder or split differently if a maintainer would rather see them together.
Joob1n commentedon Aug 17, 2026
Adding the provenance, since my claim above only described the plan.
I filed this issue from the review on the closed #2954, which was my own attempt at this adapter. Everything in the description came out of that round rather than from reading the code fresh — the transcript-shape details (one response split across records sharing
message.id, tool results arriving astype: 'user', sidechains being whole files), the measured counts, and above all the Ledger constraint, which took several passes to isolate.That last one is the part worth not rediscovering.
isTrustworthyRecoveredTerminalis only half of it:terminalStatusinruntime-event-backfill.tsindependently refuses to emit a terminal event without a recordedturn_state, so relaxing only the first leaves the Run atfailedand looks like the fix did not work. I have a branch where both are open and the imports materialize correctly.So I would like to keep this one. If someone else is interested in the area, I would rather split by layer than hand over the whole thing — the Ledger change is genuinely separable and could go to a different pair of hands than the adapter.
feat(storage): import Claude Code transcripts as Maka Sessions (#3435)
Astro-Han commentedon Aug 23, 2026
Closing as completed by #3435. The merged adapter imports Claude Code transcripts through the existing source-agnostic import authority, handles split assistant records, tool-result records, sidechains and cwd identity, and derives terminal state from recorded evidence without weakening the Runtime ledger boundary.\n\nThe separate large-catalog usability consequence is tracked by #3438 and does not leave this adapter issue incomplete.\n\nTriage and wording were prepared with OpenAI Codex and reviewed by the maintainer.
feat(storage): import opencode sessions through ExternalSessionAdapter (