[Windows] Archiving fails with os error 2 when rollout paths use the \\?\ prefix; external path normalization is ineffective

Resolved 💬 22 comments Opened Aug 18, 2026 by RuriLothlorien Closed Aug 24, 2026
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Summary

On Windows, Codex Desktop cannot archive local conversations when the stored rollout path uses the Windows extended-length prefix \\?\. The archive request fails with a thread-store os error 2 even though the rollout file exists on disk.

Environment

  • Product: Codex Desktop (Microsoft Store / WindowsApps install)
  • App package: OpenAI.Codex_26.814.5167.0_x64__2p2nqsd0c76g0
  • Core (app-server): codex 26.814.41407
  • OS: Windows 11 x64, NTFS
  • System locale: zh-CN (error text appears as 系统找不到指定的文件)
  • Threads: local project threads created through the normal desktop workflow

Actual behavior

  • Clicking Archive shuts down the active thread instance, then returns:

failed to archive session: thread-store internal error: failed to archive thread: The system cannot find the file specified. (os error 2)

  • The conversation stays visible and state_5.sqlite.threads.archived remains 0.
  • The referenced rollout .jsonl exists on disk, and the archived_sessions directory exists.

Root-cause evidence

  • state_5.sqlite (threads.rollout_path) contains a mix of path forms:
  • Failing unarchived threads: \\?\C:\Users\<user>\.codex\sessions\...
  • Previously archived threads: plain C:\Users\<user>\.codex\sessions\... (these archive successfully)
  • The extended form is actively re-persisted by the running app, not a one-time corruption:
  • An external daemon strips \\?\ from state_5.sqlite every ~3 s.
  • The running app writes the extended form back within ~16 s for the active/resumed thread.
  • Logs show: codex_rollout::recorder: Resuming rollout from "\\?\C:\Users\<user>\.codex\sessions\...jsonl".
  • OS-level file moves are not the problem: os.rename succeeds for all four combinations (\\?\→plain, plain→plain, \\?\\\?\, plain→\\?\) on the same files. The failure is in Codex's path comparison/construction logic, not NTFS.
  • Threads created during this app run are stored with the extended form; older threads keep plain paths until they are resumed, after which the extended form returns.

Reproduction

  1. Windows Codex Desktop 26.814.5167.0, local project with existing conversations.
  2. Open a conversation whose state_5.sqlite rollout_path uses \\?\C:\....
  3. Archive it from the conversation menu.
  4. Observe os error 2; the thread remains unarchived.

Attempted workarounds and their effectiveness

  1. External daemon rewriting state_5.sqlite to strip \\?\ from rollout_path/cwd every ~3 s.
  • Effectiveness: not effective while the app is running. The app re-persists the extended form within ~16 s for active/resumed threads. It only prevents the DB from accumulating prefixed rows.
  1. Fully quit Codex → normalize the DB while stopped → restart → archive before opening/resuming the thread.
  • Effectiveness: temporarily works for threads that are already affected (their rows are plain at startup and they archive successfully), but fails again after the thread is resumed, because the extended path returns. Not a durable fix.
  1. Keeping threads closed and archiving immediately after a clean restart.
  • Effectiveness: partial; cannot help the currently active thread, and any thread resumed afterwards regresses.

Expected behavior

Archiving should either normalize Windows extended-length paths before the move, or the thread store should stop persisting \\?\-prefixed paths in the first place. Previously archived threads with plain paths do not exhibit this failure, so normalizing at read/archive time should be sufficient.

Related issues

  • #39150 - Cannot archive conversations when rollout paths use the \\?\ prefix
  • #39130 - Local thread archiving fails with thread-store os error 2
  • #26012 - Windows archive/restore path inconsistency
  • #31074 - Thread store resolves stale session_index entries to missing rollout files
  • #25509 - Related merged fix for Windows running-thread resume path normalization
  • #36531 - Archiving a thread fails when the archive move crosses filesystems

View original on GitHub ↗

22 Comments

github-actions[bot] contributor · 9 days ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #39150
  • #39130

Powered by Codex Action

RuriLothlorien · 9 days ago

This issue is intentionally kept as a complementary, consolidated report: it focuses on the empirical record of the attempted workarounds and why they are ineffective, rather than restating the original reproduction.

The key additional data points beyond #39150 / #39130 are:

  • The \\?\ prefix is actively re-persisted by the running app: stripping it from state_5.sqlite every ~3 s does not stick; the app writes the extended form back within ~16 s for the active/resumed thread.
  • OS-level rename works for every combination of extended/plain path forms, which narrows the bug down to Codex's path handling rather than NTFS.
  • The only currently working procedure (quit app → normalize DB → restart → archive before resuming the thread) is temporary and regresses as soon as the thread is resumed.

Happy for maintainers to close this as a duplicate if preferred; the practical value is the workaround-effectiveness record above, which is also summarized in the #39150 thread.

shleder · 9 days ago

The Windows extended-length prefix (\\?\) persistence mismatch in state_5.sqlite.threads.rollout_path is closely related to path canonicalization diagnostics in Codex Rescue. Rescue includes normalized path family checking (WORKSPACE_CONTEXT_MISMATCH / Windows path normalization) to diagnose affected rollouts without mutating SQLite or filesystem state.

You can inspect your local session paths and check diagnostic integrity across your active and archived threads via the read-only Alpha6 release:

npx --yes codex-rescue doctor --all

Or view workspace environment state on affected threads:

npx --yes codex-rescue workspace --latest

It operates strictly read-only and local-first. If you try it, sharing whether it correctly identifies the normalized path boundaries on your affected threads would be very valuable.

RuriLothlorien · 9 days ago

Tested Codex Rescue on this machine as suggested (read-only commands only). Quick feedback:

  • codex-rescue@0.1.0-alpha.6 (npm latest) fails to launch on Windows x64: platform package codex-rescue-win32-x64 is not installed, and codex-rescue-win32-x64 returns 404 on the npm registry. codex-rescue@0.1.0-alpha.6-2 launches fine.
  • doctor --all: 17 sessions scanned, 10 healthy, 7 with findings (mostly INVALID_PERSISTED_ITEM_ID, plus INTERRUPTED_INPUT_NOT_DURABLE, UNFINISHED_TOOL_CALL, INCOMPLETE_ROLLOUT).
  • Targeted doctor + workspace on the two threads that currently fail to archive with the \\?\-prefixed rollout path:
  • One reports INVALID_PERSISTED_ITEM_ID; the other is HEALTHY.
  • workspace reports Saved Working Directory: unavailable (unknown) and Workspace Association Health: UNRECORDED for both, so it does not currently surface the \\?\ extended-path boundary on these affected legacy threads.
  • The archive failure itself remains reproducible and appears to live in the thread-store path handling; Rescue's current checks did not flag it on this machine.

Happy to share more (sanitized) output if useful.

Pdz1 · 9 days ago

Confirmed independently on Codex Desktop 26.814.41407.

Platform: Microsoft Windows NT 10.0.22631.0 x64.

I observed the same behavior:

  • state_5.sqlite stored the affected rollout_path using the \\?\C:\... form;
  • the referenced rollout JSONL existed through both path representations;
  • archiving failed with os error 2;
  • removing only the \\?\ prefix allowed an inactive thread to archive successfully;
  • for the currently active thread, Codex wrote the \\?\ prefix back into SQLite almost immediately;
  • the prefixed path also returned after restarting Codex Desktop.

I also encountered “thread not found” for an affected existing thread. No rollout JSONL files were missing.

N2meLess · 9 days ago

Still reproducible on Codex App 26.818.11542 on Windows. I independently observed the same \\?\ rollout path followed by os error 2 when archiving. The referenced JSONL file still exists, and both os.path.exists(path) and os.path.isfile(path) return True.

N2meLess · 9 days ago

Additional data point: I was able to archive one older thread successfully, then unarchive/resume it successfully. After that, its stored rollout_path was \\?\C:\..., and a second attempt to archive the same thread now fails with the same os error 2. I did not capture the DB path immediately before the first successful archive, so I can't prove the path rewrite is the sole trigger, but the failure appeared after the unarchive/resume cycle.

RuriLothlorien · 9 days ago

Consolidating the latest independent reports (thanks @Pdz1 and @N2meLess) into one picture for maintainers:

  1. Multiple independent reproductions (Windows, Codex Desktop 26.814.x): same signature everywhere — state_5.sqlite.threads.rollout_path stored as \\?\C:\..., rollout JSONL present, archive fails with os error 2. Stripping the prefix allows an inactive thread to archive successfully; the active thread gets the prefix re-persisted almost immediately; the prefix also returns after an app restart.
  2. Still reproducible on 26.818.11542 (reported by N2meLess), so this is not fixed in the newest build either.
  3. The trigger is now fairly clear: the reported sequence "archive OK → unarchive/resume → rollout_path becomes \\?\C:\... → second archive fails" matches our local observation that resume re-persists the extended form. The failure appears right after a resume/unarchive cycle, i.e. when the thread store writes the rollout path back.

Suggested fix directions:

  • When persisting rollout_path (especially on resume/unarchive), normalize Windows extended-length paths back to plain C:\... form, or
  • Normalize \\?\ in archive_thread before the rename/comparison (e.g. in scoped_rollout_path / path-scope checks), since previously archived threads with plain paths archive fine.

This is no longer an isolated environment quirk: the same extended-path persistence and archive failure are reproducible across at least three machines/builds.

starkovalera · 8 days ago

Additional independent reproduction on Windows Codex Desktop:

  • Package: OpenAI.Codex_26.814.5517.0_x64
  • The issue reproduces after fully quitting and restarting Codex; restart does not clear it.
  • A fresh local recipe-manager thread is stored with a rollout_path using the Windows verbatim prefix \\?\C:\...
  • The rollout JSONL exists at the equivalent normal Windows path.
  • Clicking Archive returns: failed to archive session: thread-store internal error: failed to archive thread: The system cannot find the file specified. (os error 2)
  • Normalizing only rollout_path by removing the \\?\ prefix, then invoking the normal Archive action immediately, succeeds and moves the rollout to archived_sessions.
  • New threads are affected too; this is not limited to migrated historical sessions.

This confirms the path form is actively produced by the current Desktop build and that the failure is in archive path handling. No chat contents, private paths, credentials, or source files are attached.

shleder · 8 days ago

Thanks again @RuriLothlorien — your test exposed two real Rescue issues, and both are now addressed in the Alpha6 field-fix release (0.1.0-alpha.6-3).

The Windows launcher now resolves the published codex-rescue-windows-x64 package directly, and Rescue now checks the C:\... / \\?\C:\... rollout-path identity boundary (WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCE) instead of treating a structurally healthy JSONL transcript as globally healthy when the thread-store path has diverged.

If you still have one of the affected threads locally, a read-only rerun would be very useful:

npx --yes codex-rescue --version
npx --yes codex-rescue doctor --all

For the affected thread, the main thing I’m interested in is whether Rescue now surfaces:

WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCE

Sanitized finding codes/output are enough — no raw rollout files, prompts, SQLite databases, or private paths needed.

shleder · 8 days ago

@Pdz1 @N2meLess — thanks for the independent Windows confirmations. The current Rescue Alpha6 field-fix release (0.1.0-alpha.6-3) now has a dedicated read-only diagnostic for this path/thread-store identity class.

If either affected session is still available, could you try:

npx --yes codex-rescue doctor --all

The expected Rescue-side finding for a proven C:\... / \\?\C:\... identity divergence is:

WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCE

This does not rewrite Codex SQLite or claim to repair the upstream archive failure. Sanitized finding codes are enough.

RuriLothlorien · 8 days ago

Thanks @shleder — the new WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCE check is working as intended. Confirmed on this Windows machine with codex-rescue@0.1.0-alpha.6-3:

  • The code is reported for every local thread whose state_5.sqlite.threads.rollout_path is currently stored in the \\?\C:\... form — 4/4 threads here, including two that reproduce the archive os error 2 and one newly created thread.
  • Targeted doctor <rollout-file> runs surface the code directly; no raw paths, rollout content, or database files are needed for the finding.

Two small observations for the tool:

  • Batch doctor --all did not list these threads or emit the code in my runs; only targeted doctor <rollout-file> surfaced it. If the batch enumerates sessions differently (e.g., only catalog/index-discoverable ones), that may be worth a look.
  • One affected thread also reported MALFORMED_RECORD alongside the divergence; it was actively being appended at scan time, so that reads as a live-writer tail rather than corruption.
RuriLothlorien · 7 days ago

Quick consolidation of the latest independent confirmations (thanks @Culacco and @fireking0608-spec):

  • Still present in the current Desktop package (26.814.5517.0, bundled CLI 0.148.0-alpha.15) across multiple Windows builds.
  • Full consistency checks (e.g. 132 threads / 132 rollout files, SQLite integrity ok) rule out missing files or DB corruption.
  • Migrating legacy rollouts to paginated history does not prevent recurrence — the \\?\C:\... form is re-persisted by the app after resume/use, and the archive failure returns.
  • Removing the \\?\ prefix while Codex is stopped makes the same backend archive/unarchive successfully.
  • Locally confirmed that the archive/unarchive round-trip works with a newer standalone CLI (codex-cli 0.149.0-alpha.1) that contains the archive-path fix; 0.148.0-alpha.15 still fails on verbatim paths.

Net: the Desktop package needs (a) archive-path normalization in its bundled thread-store, and (b) to stop re-persisting \\?\C:\... rollout paths on resume.

M-T-D-N · 7 days ago

Independent confirmation on Desktop 26.818.22352

I independently reproduced the same defect on another Windows x64 installation. This adds newer-build persistence and wider-state evidence:

  • Five failed thread/archive attempts stopped after thread-listener teardown. Every affected row had threads.rollout_path in the \\?\C:\... form.
  • After validating that every target resolved to an existing file inside the Codex session root, I transactionally normalized 25 affected active rows to the ordinary drive form. Immediately after that transaction, the count of verbatim-form active rollout paths was 0.
  • A canary archive through the normal Desktop/App Server API then succeeded. The rollout moved to the archived-session directory and the database archive state matched.
  • After subsequent normal thread-list activity, 9 active rows were persisted again in the verbatim form. This confirms that one-time database normalization is only a per-target workaround; ordinary app activity recreates the precondition.
  • PRAGMA integrity_check returned ok. The wider audit found no database rows with missing rollout files, no archived-flag/location mismatches, no filename/thread-ID mismatches, and no duplicate canonical rollout paths.

This supports the issue's existing conclusion: the source file is not missing, and inconsistent Windows path representation across persistence/list and archive stages is the likely proximate cause. The exact writer function and exact failing archive-stage operation remain inferred rather than source-traced.

A durable fix should normalize at the threads.rollout_path persistence boundary and make archive path handling representation-independent. Regression coverage should include ordinary drive paths, verbatim drive paths, verbatim UNC paths, list/resume idempotence, and archive/unarchive round trips.

No user paths, project names, thread/session IDs, raw logs, session contents, or database files are included.

AI disclosure

At the user's direction, this report transparently records that the user authorized the submission and completed the GitHub authentication step, while the investigation, analysis, drafting, and GitHub posting of this technical report were performed by an AI agent (OpenAI Codex) on the user's behalf.

shleder · 6 days ago

@RuriLothlorien

Thanks for testing so thoroughly on Windows, and for confirming the WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCE check works on targeted runs. Status on your three points in the just-released 0.1.0-alpha.7:

  1. npm launch failure (codex-rescue-win32-x64 404) — fixed. The launcher now resolves codex-rescue-windows-x64 first (the published package), with the old name as fallback. npx --yes codex-rescue@0.1.0-alpha.7 should launch cleanly on Windows x64.
  1. \\?\ extended-path divergence not surfaced on legacy threads — the divergence detection is in and, as you confirmed, targeted doctor <rollout-file> surfaces it. Alpha7 also hardened Windows extended-path normalization for this class.
  1. doctor --all batch not listing these threads — you're right, and this is NOT fully fixed yet. The batch path still enumerates via filesystem glob of the sessions directories rather than merging the SQLite thread inventory, so threads discoverable only through the index (or whose on-disk path form diverges) can be skipped in --all. Targeted doctor <rollout-file> remains the reliable way to surface the divergence for now. I'm tracking the batch-enumeration gap and will fold inventory-merged enumeration into a follow-up — I'd rather say it's not done than claim it is.

On your MALFORMED_RECORD observation: agreed, a record being appended at scan time reads as a live-writer tail, not corruption. Alpha7's writer diagnostics (codex-rescue writer --latest) can confirm an active writer on that thread so the tail can be interpreted correctly instead of flagged.

If you re-run on 0.1.0-alpha.7, the targeted-run behavior should match what you confirmed on alpha.6-3, plus the clean npm launch. Any further sanitized output is welcome.

shleder · 6 days ago

@RuriLothlorien

Root cause was that run_doctor_all / run_doctor_changed used a shallow sessions/*.jsonl glob, so rollouts under date subdirs (sessions/YYYY/MM/DD/rollout-*.jsonl) and any diverged-path threads were silently skipped in --all while targeted doctor <file> surfaced them. The fix switches batch enumeration to recursive rglob("*.jsonl") plus the canonical _rollout_paths walk, deduped — doctor_session itself already joins the SQLite thread_store via path_identity, so \\?\C:\... identity-diverged rows are now surfaced in batch as well.

doctor --all should now list those 4 threads you confirmed on alpha.6-3 (including the two archive os error 2 cases) without needing targeted runs. Still read-only — thanks again for the precise report that made this reproducible.

RuriLothlorien · 6 days ago

Thanks @shleder — reran on codex-rescue@0.1.0-alpha.7 as requested (read-only). Results from this Windows machine:

  • npm launch: fixednpx --yes codex-rescue@0.1.0-alpha.7 now starts cleanly on Windows x64 (no codex-rescue-win32-x64 404).
  • Targeted runs: still correctdoctor <rollout-file> surfaces WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCE on every thread whose state_5.sqlite.threads.rollout_path is currently in the \\?\C:\... form (3/3 tested here, including a thread created today).
  • Batch: improved but not fully fixed on this machinedoctor --all now scans more sessions (25 vs ~20), but doctor --all --json still contains 0 WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCE (3 THREAD_IDENTITY_CONFLICT entries appear), and the currently-prefixed threads are still not listed by thread id in the batch output.

So the recursive enumeration change helped coverage, but on this machine the divergence code still only appears via targeted doctor <rollout-file> runs. Happy to provide more sanitized output if useful.

RuriLothlorien · 5 days ago

Fixed-version confirmation (Windows):

  • Desktop package: 26.818.5229.0; bundled/standalone CLI: codex-cli 0.149.0-alpha.4.1.
  • Successfully archived threads whose state_5.sqlite.threads.rollout_path was still in the \\?\C:\... form (including a thread that previously failed with os error 2): thread/archive returned success, the rollout moved to archived_sessions, and archived=1 with a plain path.
  • No os error 2 / archive failures in the logs after this version.
  • Note: the app still persists \\?\C:\... paths for new/active threads (14 such rows remain here), but archive now handles them correctly — i.e. archive-time path handling is representation-independent now, while the persistence-side normalization suggested earlier does not appear to be implemented yet.

This matches the expected fix direction (handle the verbatim form at archive/read time) and resolves the practical failure on current builds.

RuriLothlorien · 3 days ago

Successfully archived threads

shleder · 3 days ago

Your Windows testing is preserved as regression coverage: recursive batch discovery now includes date-partitioned rollouts, and Vetto reports WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCE through a read-only SQLite inventory check.

Migration update: active Codex Rescue development has moved to Vetto: https://github.com/shleder/vetto. The standalone shleder/codex-rescue repository remains public as compatibility history. User installation is now npm install --global @shleddy/vetto@next, with recovery under vetto rescue. The boundary remains read-only/copy-only and does not modify session JSONL or vendor SQLite. The newest diagnostic changes are merged into Vetto main and will be included in a later npm alpha; no source install is requested.

RuriLothlorien · 3 days ago

@shleder — I tested the newly published @shleddy/vetto@next (0.2.0-alpha.2) on this Windows machine, read-only. Results:

Batch discovery — fixed

  • vetto rescue scan now enumerates 59 sessions, including all 12 threads on this machine whose state_5.sqlite.threads.rollout_path is stored in the \\?\C:\... form. Previously codex-rescue doctor --all (alpha.7) skipped these and only targeted doctor <file> surfaced them.
  • Date-partitioned rollouts (sessions/YYYY/MM/DD/...) are included in the batch listing.

Divergence diagnostic — not in this npm alpha yet

  • vetto rescue diagnose <key> on two affected verbatim-prefixed threads (including the currently active one) returns health: healthy, notices: [], and does not emit WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCE.
  • This matches your note that the SQLite-inventory divergence check is merged into Vetto main and will land in a later npm alpha.

Also noting the CLI rename: doctor is now rescue scan / rescue diagnose.

Happy to rerun once a newer alpha with the inventory check ships. Thanks for moving this forward.

shleder · 1 day ago

@RuriLothlorien thank you for the structured field test of 0.2.0-alpha.2 - exactly the reporting format that makes triage fast. Since your run: 0.2.0 went stable on npm, and main gained DUPLICATE_ORDINAL_BOUNDARY / ORDINAL_REGRESSION detection in diagnose for the frozen-history family from #35746. If anything odd shows up in further testing, the redaction rules are in docs/field-testing.md and issues go straight to the repo.