Skip to content

Claude Code transcripts cannot be imported as Maka Sessions #3142

Description

@Joob1n

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 a tool_result. Folding only consecutive records imports one answer as several rows — 383 occurrences across 33 of 1236 local transcripts, 378 of them continuations carrying only tool_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 isSidechain files, 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 cwd can answer a project-scoped query.
  • isMeta records 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:

  1. Adapters emit turn_state only for facts the transcript carries — the interrupt notice and isApiErrorMessage — and let deriveTurnRecords label everything else inferred.
  2. The Ledger accepts an inferred terminal on the transcript-materialization path, where transcriptRunHeader derives 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 and tool_use in one record where 2.1 splits them.
  • Reuse claudeUserMessageText and the interrupt literal from @maka/core/foreign-session rather than re-implementing them; the scanner and the importer deciding "what did the user say" differently would be a real bug.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions