[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.archivedremains0. - The referenced rollout
.jsonlexists on disk, and thearchived_sessionsdirectory 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
\\?\fromstate_5.sqliteevery ~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.renamesucceeds 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
- Windows Codex Desktop 26.814.5167.0, local project with existing conversations.
- Open a conversation whose
state_5.sqliterollout_pathuses\\?\C:\.... - Archive it from the conversation menu.
- Observe
os error 2; the thread remains unarchived.
Attempted workarounds and their effectiveness
- External daemon rewriting
state_5.sqliteto strip\\?\fromrollout_path/cwdevery ~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.
- 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.
- 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
22 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
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:
\\?\prefix is actively re-persisted by the running app: stripping it fromstate_5.sqliteevery ~3 s does not stick; the app writes the extended form back within ~16 s for the active/resumed thread.renameworks for every combination of extended/plain path forms, which narrows the bug down to Codex's path handling rather than NTFS.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.
The Windows extended-length prefix (
\\?\) persistence mismatch instate_5.sqlite.threads.rollout_pathis 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:
Or view workspace environment state on affected threads:
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.
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, andcodex-rescue-win32-x64returns 404 on the npm registry.codex-rescue@0.1.0-alpha.6-2launches fine.doctor --all: 17 sessions scanned, 10 healthy, 7 with findings (mostlyINVALID_PERSISTED_ITEM_ID, plusINTERRUPTED_INPUT_NOT_DURABLE,UNFINISHED_TOOL_CALL,INCOMPLETE_ROLLOUT).doctor+workspaceon the two threads that currently fail to archive with the\\?\-prefixed rollout path:INVALID_PERSISTED_ITEM_ID; the other isHEALTHY.workspacereportsSaved Working Directory: unavailable (unknown)andWorkspace Association Health: UNRECORDEDfor both, so it does not currently surface the\\?\extended-path boundary on these affected legacy threads.Happy to share more (sanitized) output if useful.
Confirmed independently on Codex Desktop 26.814.41407.
Platform: Microsoft Windows NT 10.0.22631.0 x64.
I observed the same behavior:
I also encountered “thread not found” for an affected existing thread. No rollout JSONL files were missing.
Still reproducible on Codex App 26.818.11542 on Windows. I independently observed the same
\\?\rollout path followed byos error 2when archiving. The referenced JSONL file still exists, and bothos.path.exists(path)andos.path.isfile(path)returnTrue.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.
Consolidating the latest independent reports (thanks @Pdz1 and @N2meLess) into one picture for maintainers:
state_5.sqlite.threads.rollout_pathstored 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.\\?\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:
rollout_path(especially on resume/unarchive), normalize Windows extended-length paths back to plainC:\...form, or\\?\inarchive_threadbefore the rename/comparison (e.g. inscoped_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.
Additional independent reproduction on Windows Codex Desktop:
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.
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-x64package directly, and Rescue now checks theC:\.../\\?\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:
For the affected thread, the main thing I’m interested in is whether Rescue now surfaces:
WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCESanitized finding codes/output are enough — no raw rollout files, prompts, SQLite databases, or private paths needed.
@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:
The expected Rescue-side finding for a proven
C:\.../\\?\C:\...identity divergence is:WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCEThis does not rewrite Codex SQLite or claim to repair the upstream archive failure. Sanitized finding codes are enough.
Thanks @shleder — the new
WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCEcheck is working as intended. Confirmed on this Windows machine withcodex-rescue@0.1.0-alpha.6-3:state_5.sqlite.threads.rollout_pathis currently stored in the\\?\C:\...form — 4/4 threads here, including two that reproduce the archiveos error 2and one newly created thread.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:
doctor --alldid not list these threads or emit the code in my runs; only targeteddoctor <rollout-file>surfaced it. If the batch enumerates sessions differently (e.g., only catalog/index-discoverable ones), that may be worth a look.MALFORMED_RECORDalongside the divergence; it was actively being appended at scan time, so that reads as a live-writer tail rather than corruption.Quick consolidation of the latest independent confirmations (thanks @Culacco and @fireking0608-spec):
26.814.5517.0, bundled CLI0.148.0-alpha.15) across multiple Windows builds.\\?\C:\...form is re-persisted by the app after resume/use, and the archive failure returns.\\?\prefix while Codex is stopped makes the same backend archive/unarchive successfully.codex-cli 0.149.0-alpha.1) that contains the archive-path fix;0.148.0-alpha.15still 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.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:
thread/archiveattempts stopped after thread-listener teardown. Every affected row hadthreads.rollout_pathin the\\?\C:\...form.PRAGMA integrity_checkreturnedok. 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_pathpersistence 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.
@RuriLothlorien
Thanks for testing so thoroughly on Windows, and for confirming the
WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCEcheck works on targeted runs. Status on your three points in the just-released 0.1.0-alpha.7:codex-rescue-win32-x64404) — fixed. The launcher now resolvescodex-rescue-windows-x64first (the published package), with the old name as fallback.npx --yes codex-rescue@0.1.0-alpha.7should launch cleanly on Windows x64.\\?\extended-path divergence not surfaced on legacy threads — the divergence detection is in and, as you confirmed, targeteddoctor <rollout-file>surfaces it. Alpha7 also hardened Windows extended-path normalization for this class.doctor --allbatch 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. Targeteddoctor <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_RECORDobservation: 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.
@RuriLothlorien
Root cause was that
run_doctor_all/run_doctor_changedused a shallowsessions/*.jsonlglob, so rollouts under date subdirs (sessions/YYYY/MM/DD/rollout-*.jsonl) and any diverged-path threads were silently skipped in--allwhile targeteddoctor <file>surfaced them. The fix switches batch enumeration to recursiverglob("*.jsonl")plus the canonical_rollout_pathswalk, deduped —doctor_sessionitself already joins the SQLitethread_storeviapath_identity, so\\?\C:\...identity-diverged rows are now surfaced in batch as well.doctor --allshould now list those 4 threads you confirmed on alpha.6-3 (including the two archiveos error 2cases) without needing targeted runs. Still read-only — thanks again for the precise report that made this reproducible.Thanks @shleder — reran on
codex-rescue@0.1.0-alpha.7as requested (read-only). Results from this Windows machine:npx --yes codex-rescue@0.1.0-alpha.7now starts cleanly on Windows x64 (nocodex-rescue-win32-x64404).doctor <rollout-file>surfacesWINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCEon every thread whosestate_5.sqlite.threads.rollout_pathis currently in the\\?\C:\...form (3/3 tested here, including a thread created today).doctor --allnow scans more sessions (25 vs ~20), butdoctor --all --jsonstill contains 0WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCE(3THREAD_IDENTITY_CONFLICTentries 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.Fixed-version confirmation (Windows):
26.818.5229.0; bundled/standalone CLI:codex-cli 0.149.0-alpha.4.1.state_5.sqlite.threads.rollout_pathwas still in the\\?\C:\...form (including a thread that previously failed with os error 2):thread/archivereturned success, the rollout moved toarchived_sessions, andarchived=1with a plain path.\\?\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.
Successfully archived threads
Your Windows testing is preserved as regression coverage: recursive batch discovery now includes date-partitioned rollouts, and Vetto reports
WINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCEthrough 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-rescuerepository remains public as compatibility history. User installation is nownpm install --global @shleddy/vetto@next, with recovery undervetto rescue. The boundary remains read-only/copy-only and does not modify session JSONL or vendor SQLite. The newest diagnostic changes are merged into Vettomainand will be included in a later npm alpha; no source install is requested.@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 scannow enumerates 59 sessions, including all 12 threads on this machine whosestate_5.sqlite.threads.rollout_pathis stored in the\\?\C:\...form. Previouslycodex-rescue doctor --all(alpha.7) skipped these and only targeteddoctor <file>surfaced them.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) returnshealth: healthy,notices: [], and does not emitWINDOWS_ROLLOUT_PATH_IDENTITY_DIVERGENCE.mainand will land in a later npm alpha.Also noting the CLI rename:
doctoris nowrescue scan/rescue diagnose.Happy to rerun once a newer alpha with the inventory check ships. Thanks for moving this forward.
@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.