Documentation
¶
Overview ¶
Package migrate converts coding-agent sessions between Claude Code and OpenAI Codex so work started with one agent can be resumed with the other.
The two directions are deliberately asymmetric:
- Codex -> Claude: Claude Code has no import machinery, so zen translates the Codex rollout itself into a Claude-format session file placed where `claude --resume` discovers it (see codex_read.go / claude_write.go).
- Claude -> Codex: Codex ships its own Claude Code importer, so zen drives it over the `codex app-server` JSON-RPC API and never writes Codex's files itself (see codex_import.go).
Both session formats are undocumented internals of their CLIs. The record shapes handled here were verified against Claude Code 2.1.200 and Codex CLI 0.142.0; unknown record types are skipped rather than failing the migration.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ClaudeToCodex ¶
ClaudeToCodex makes a Claude Code session resumable in Codex and returns the Codex thread id to pass to `codex resume`.
Codex owns this direction: its importer converts Claude sessions into native rollout threads and records them in an import ledger. zen first checks the ledger (the session may already be imported — imports are content-addressed by sha256), and otherwise drives the importer over the `codex app-server` JSON-RPC API. zen never writes to ~/.codex itself.
Known importer limits (Codex 0.142.0): only the ~50 most recent sessions from the last 30 days are detected, and sessions whose recorded cwd no longer exists on disk are skipped.
func CodexToClaude ¶
CodexToClaude migrates a Codex rollout into a new Claude Code session for the worktree and returns the session id to pass to `claude --resume`.
func WriteClaudeSession ¶
func WriteClaudeSession(t *Transcript, worktreePath string) (string, error)
WriteClaudeSession renders a transcript as a Claude Code session file under ~/.claude/projects/<munged worktree>/<session-id>.jsonl and returns the session id. The worktree must exist: Claude derives the directory name from the symlink-resolved path, and resume only finds files in that exact dir.
Types ¶
type Message ¶
type Message struct {
Kind MsgKind
// Text holds the message body for UserText/AssistantText and the output
// for ToolResult.
Text string
// Time is the source record's RFC3339 timestamp, empty if unknown.
Time string
// ToolName and ToolInput describe a ToolCall in the source agent's terms
// (e.g. Codex "exec_command" with {"cmd": ...}).
ToolName string
ToolInput map[string]any
// CallID pairs a ToolCall with its ToolResult.
CallID string
}
Message is one conversation step in the agent-neutral transcript.
type MsgKind ¶
type MsgKind int
MsgKind classifies a transcript message.
const ( // UserText is a plain user message. UserText MsgKind = iota // AssistantText is a plain assistant message. AssistantText // ToolCall is an assistant tool invocation. In a well-formed transcript it // is immediately followed by its ToolResult (readers guarantee adjacency, // synthesizing a placeholder result when the source recorded none). ToolCall // ToolResult carries the output of the preceding ToolCall. ToolResult )
type Transcript ¶
type Transcript struct {
// SourceID is the session/thread id in the source agent.
SourceID string
// Cwd is the working directory the source session recorded.
Cwd string
// GitBranch is the branch the source session recorded, if any.
GitBranch string
Messages []Message
}
Transcript is the neutral intermediate representation of a session.
func ReadCodexRollout ¶
func ReadCodexRollout(path string) (*Transcript, error)
ReadCodexRollout parses a Codex rollout file into a neutral transcript. Records that carry no portable conversation content are skipped: encrypted reasoning, event_msg UI replays, turn_context, and Codex-injected context messages. Every ToolCall in the result is immediately followed by its ToolResult; a placeholder is synthesized when the rollout recorded no output.