openai/codex · error · std::io::Error

invalid external agent session import ledger: {err}

Error message

invalid external agent session import ledger: {err}

What it means

load_import_ledger (codex-rs/external-agent-migration/src/sessions/ledger.rs:373-390) reads the session-import ledger JSON under codex_home. A missing file is normal (an empty default ledger is returned), but content that fails serde parsing becomes io::Error(InvalidData) with this message. Because every session-import API - detect_recent_sessions, has_current_session_been_imported, find_existing_session_import, checkpoint_existing_session_import, record_completed_session_imports, record_detected_session_connectors - loads the ledger, one unreadable file blocks the entire import feature.

Source

Thrown at codex-rs/external-agent-migration/src/sessions/ledger.rs:385

        record.source_modified_at = Some(source_modified_at);
        self.records.push(record);
        Ok(true)
    }
}

pub(crate) fn load_import_ledger(
    codex_home: &Path,
) -> io::Result<ImportedExternalAgentSessionLedger> {
    let path = import_ledger_path(codex_home);
    let raw = match fs::read_to_string(path) {
        Ok(raw) => raw,
        Err(err) if err.kind() == io::ErrorKind::NotFound => {
            return Ok(ImportedExternalAgentSessionLedger::default());
        }
        Err(err) => return Err(err),
    };
    serde_json::from_str(&raw).map_err(|err| {
        io::Error::new(
            io::ErrorKind::InvalidData,
            format!("invalid external agent session import ledger: {err}"),
        )
    })
}

pub(crate) fn save_import_ledger(
    codex_home: &Path,
    ledger: &ImportedExternalAgentSessionLedger,
) -> io::Result<()> {
    fs::create_dir_all(codex_home)?;
    let path = import_ledger_path(codex_home);
    let raw = serde_json::to_vec_pretty(ledger).map_err(io::Error::other)?;
    fs::write(path, raw)
}

fn import_ledger_path(codex_home: &Path) -> PathBuf {
    codex_home.join(SESSION_IMPORT_LEDGER_FILE)

View on GitHub (pinned to 339751715c)

Solutions

  1. Back up and delete the ledger file under codex_home - it regenerates as an empty ledger; the trade-off is prior import records are forgotten, so already-imported sessions may be offered again
  2. Alternatively repair the JSON to satisfy the ledger struct if the records matter (truncation usually just drops the closing bracket)
  3. Fix the root cause (free disk space; avoid killing the process mid-import) before re-importing so the rewrite cannot corrupt again

Example fix

// recovery: back up the ledger so it regenerates empty
let path = import_ledger_path(codex_home); // codex_home/<SESSION_IMPORT_LEDGER_FILE>
std::fs::rename(&path, path.with_extension("json.bak"))?;
// next load_import_ledger call returns a fresh default ledger
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: confirm the ledger parses before invoking import APIs
let path = codex_home.join(SESSION_IMPORT_LEDGER_FILE);
if let Ok(raw) = std::fs::read_to_string(&path) {
    if serde_json::from_str::<serde_json::Value>(&raw).is_err() {
        // quarantine the file now instead of failing later mid-import
    }
}

Try / catch

match load_import_ledger(codex_home) {
    Err(err) if err.kind() == io::ErrorKind::InvalidData => {
        // back up + remove the ledger, then retry once;
        // accept that previously imported sessions may be re-offered
    }
    other => other,
}

Prevention

When it happens

Trigger: The ledger file exists but is not valid JSON for the ledger schema: truncated by a crash or disk-full during save_import_ledger's plain fs::write (non-atomic), hand-edited, or written in an incompatible format by a different codex version.

Common situations: Process killed or power lost mid-import; user or a sync tool edited the file; downgrading codex after a newer version changed ledger fields; a full disk leaving a partial write.

Related errors


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/6e15ccb818349e84. Report an issue: GitHub.