Skip to content

fix: resolve imported transfer threads when the ledger record diverges (#417)#469

Open
ayobamiseun wants to merge 1 commit into
openai:mainfrom
ayobamiseun:fix/417-transfer-ledger-match
Open

fix: resolve imported transfer threads when the ledger record diverges (#417)#469
ayobamiseun wants to merge 1 commit into
openai:mainfrom
ayobamiseun:fix/417-transfer-ledger-match

Conversation

@ayobamiseun

Copy link
Copy Markdown

Fixes #417

Problem

/codex:transfer consistently fails with Codex reported that the Claude import completed, but did not record an imported thread — identically for auto-detected and explicit --source paths — even though the import actually succeeds on the Codex side.

After the externalAgentConfig/import notification fires, importedThreadIdForSource looks up the imported thread in $CODEX_HOME/external_agent_session_imports.json by strict equality on source_path (Node realpathSync output) and a content sha256 computed at lookup time. That match misses real imports two ways:

  1. Path canonicalization mismatch (Windows). Codex canonicalizes the recorded source_path itself; Rust's fs::canonicalize yields verbatim paths (\\?\C:\Users\...) that never compare equal to Node's realpathSync output (C:\Users\...). The strict path match can never succeed — matching the reporter's Git Bash / Windows 11 environment where both invocations fail byte-for-byte identically.
  2. Live-transcript hash race (all platforms). Running transfer from inside an active Claude Code session means the transcript .jsonl keeps growing (the transfer invocation itself gets logged) between Codex hashing it at import time and the companion hashing it at lookup time — so content_sha256 diverges and the record is rejected.

Either way the command exits 1 with an opaque error while a perfectly good imported thread sits in the ledger.

Fix

importExternalAgentSession snapshots the ledger before the import; when the strict match misses, the lookup falls back to records Codex appended during this import:

  • prefer an appended record whose source path matches after normalization (strip the \\?\ verbatim prefix, unify \// separators, case-fold on win32),
  • else accept a lone appended record.

The strict path+hash match stays first so Codex-side dedup of an unchanged, already-imported session still resolves without any appended record. The failure error now also names the ledger path it searched instead of only pointing at app-server logs.

Tests

New fake-codex behavior external-import-divergent-ledger simulates real Codex: the ledger record gets a verbatim-prefixed, backslash-separated source_path and a hash of content that differs from what the companion reads back (both #417 divergences at once). The new regression test:

  • fails on main: transfer exits 1 with did not record an imported thread
  • passes with this fix: resolves thr_1 and prints the resume command

All 5 transfer tests pass; full suite 91/92 — the one failure (resolveStateDir uses a temp-backed per-workspace directory) fails identically on main when run inside a live Claude Code session (pre-existing #455, unrelated).

Note: the [DEP0190] deprecation warning mentioned in the issue is a separate stderr-noise concern (#452 territory) and is not addressed here.

openai#417)

After a Claude session import completes, the companion looks up the
imported thread in Codex's external_agent_session_imports.json by
strict equality on source_path (Node realpathSync output) plus a
content sha256 computed at lookup time. That match misses successful
imports in two known ways:

- Codex canonicalizes the recorded source path itself; on Windows
  Rust's canonicalization yields verbatim paths (\\?\C:\...) that never
  compare equal to realpathSync output.
- A live Claude transcript keeps growing while the transfer runs, so
  the hash computed at lookup time trails the content Codex actually
  imported.

Both cause /codex:transfer to fail with "did not record an imported
thread" even though the import succeeded, identically for auto-detected
and explicit --source paths.

Snapshot the ledger before the import and fall back to records Codex
appended during it: prefer an appended record whose source path matches
after normalization (strip the verbatim prefix, unify separators,
case-fold on Windows), else accept a single appended record. The strict
match stays first so Codex-side dedup of unchanged sessions still
resolves. The failure error now names the ledger path it searched.

Fixes openai#417
@ayobamiseun ayobamiseun requested a review from a team July 10, 2026 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/codex:transfer fails with "did not record an imported thread" for both auto-detected and explicit --source

1 participant