[Windows] Local thread archiving fails with thread-store os error 2

Open 💬 17 comments Opened Aug 18, 2026 by sudazc

Summary

On the Windows Codex desktop app, archiving a completed local thread fails with a thread-store file-not-found error. The thread remains unarchived.

Environment

  • Product: Codex Desktop
  • Platform: Windows x64
  • App package: OpenAI.Codex_26.814.5167.0_x64__2p2nqsd0c76g0
  • Local project thread

Error

The archive request returns:

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

The desktop UI reports that the archive failed.

Steps to reproduce

  1. Open a completed local Codex thread in the Windows desktop app.
  2. Attempt to archive it from the thread/project UI.
  3. Observe the archive failure.
  4. Restart the desktop app and retry.

Actual behavior

  • The first archive attempt shuts down the active thread instance but does not persist the archive.
  • A second attempt after the thread is stopped fails with the same os error 2.
  • The thread remains visible and its archive state remains archived=0.
  • The rollout JSONL file exists on disk, and the archived_sessions directory also exists.
  • The affected rollout path uses a Windows extended-length path prefix (\\?\\C:\\...), which may be mishandled by the archive path-move logic.

Expected behavior

A completed local thread should be archived successfully, moved to the archived-session store, marked as archived, and removed from the active thread list.

Related issues

  • #36289 — Windows archive fails with a thread-store file-move error (os error 17)
  • #26012 — Windows archive/restore path inconsistency with os error 2
  • #25713 — Archiving the active Windows thread causes resume/archive state problems

No raw logs, private paths, credentials, or conversation contents are attached.

View original on GitHub ↗

17 Comments

whgm90 · 10 days ago

Root-cause follow-up for the archive failure (the sidebar-cache observation is not the primary issue here).

Confirmed local evidence

  • Codex Desktop: OpenAI.Codex_26.814.5167.0_x64 / codex-cli 0.148.0-alpha.15.
  • Immediately before the failure, the affected thread could be read/resumed and its rollout JSONL existed.
  • thread/archive failed repeatedly with:

failed to archive thread: The system cannot find the file. (os error 2).

  • A newer thread archived successfully in the same app process, so the archive directory, permissions, and archive API itself were functional.
  • The affected SQLite row stored its selected rollout as a Windows verbatim path:

\\?\D:\<redacted>\.codex\sessions\...jsonl.

  • The logical .codex location is a Windows directory junction to that physical D: location. A separate harmless probe confirmed that moving a file through this junction into archived_sessions succeeds, so the junction/cross-alias move itself is not the failing operation.

Root cause

The current archive implementation can enqueue the same physical rollout twice when the path spellings differ.

  1. RolloutReferenceIndex::scan() discovers the rollout through directory traversal using a normal (non-verbatim) path.
  2. thread_rollout_resolver::resolve_current() returns the SQLite-selected path, which in this case is the verbatim \\?\D:\... spelling.
  3. In archive_thread_with_paths(), this check is lexical:

if !rollout_paths.contains(&selected_rollout_path)
Therefore the verbatim selected path is appended even though it refers to the same file already found by the index.

  1. Each path is then passed through scoped_rollout_path(), which calls std::fs::canonicalize. On Windows, both spellings resolve to the same canonical physical source.
  2. rollout_moves now contains the same source/destination pair twice. The first std::fs::rename succeeds; the second attempts to move the already-moved source and returns NotFound / os error 2.
  3. The implementation restores earlier moves on failure, which explains why the rollout remains present/readable and every retry fails identically.

Relevant code:

Suggested fix

Canonicalize (or otherwise resolve file identity for) all candidate rollout paths before comparing/deduplicating them and before constructing rollout_moves. The selected rollout should be matched against that canonical set, and the move list should be deduplicated by canonical source/destination. A Windows regression test should cover one physical rollout supplied once as a normal path and once as a \\?\ verbatim path.

For diagnostics, it would also help if a failed move logged the redacted source/destination and move index/count.

No conversation contents, credentials, or unredacted private paths are included.

SanAntonio021 · 10 days ago

I can independently reproduce this on Windows Codex Desktop 26.814.5167.0.

Local checks show:

  • thread/archive repeatedly fails with:

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

  • The affected rollout JSONL files still exist under the active sessions store.
  • archived_sessions exists and SQLite integrity is OK.
  • The affected state records use Windows extended-length (verbatim) rollout paths.
  • Multiple retries and app restarts reproduce the failure; no files or database rows were modified while diagnosing it.

This matches the reported Windows archive/path-normalization regression. The conversations remain readable but cannot be archived from the Desktop UI.

xhw949 · 10 days ago

Additional reproduction on Windows Codex Desktop (2026-08-18).

Environment:

  • Codex Desktop package version: 26.814.5167.0
  • Windows x64
  • Local Codex host

After fully restarting Codex Desktop, archiving still failed. The same failure was reproduced on multiple separate idle local conversations:

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

Additional observations:

  • The archived-conversation listing remains functional.
  • The affected conversations remain readable and are not archived.
  • The failure is reproducible across multiple idle conversations, so it is not limited to an active-thread shutdown race.
  • No local session files or databases were modified during reproduction.

This is consistent with the Windows local archive failure described in this issue. Please investigate archive path resolution/move handling and the final archive-state persistence path.

Aramis012 · 9 days ago

Independent confirmation from another affected Windows installation, with a version-isolated A/B test.

Environment

  • Codex Desktop MSIX: 26.814.5167.0
  • Desktop release reported in logs: 26.814.41407
  • Bundled app-server/CLI: 0.148.0-alpha.15
  • Windows x64, local NTFS session storage

Reproduction and causal isolation

  • The affected rollout row used a \\?\C:\... path, while the JSONL file existed and was readable.
  • thread/archive repeatedly returned JSON-RPC -32603 with failed to archive thread ... os error 2.
  • The UI temporarily removed the thread, then restored it after the backend rollback.
  • A one-time SQLite normalization from \\?\C:\... to C:\... while the app was fully closed was not durable: cold-resuming the legacy thread canonicalized the path back to the verbatim form before the next archive attempt.

I then made an isolated copy of the affected state database and rollout subtree and ran the same app-server sequence against two valid OpenAI-signed runtimes:

initialize -> thread/list -> thread/read -> thread/resume -> thread/archive

Results:

  • 0.148.0-alpha.15: fails reproducibly with the same -32603 / os error 2.
  • Stable 0.147.0: succeeds on the identical isolated state, including the root rollout and active descendants.

As a real-world confirmation, the desktop app was temporarily pointed at the signed 0.147.0 runtime and fully restarted. The logs reported currentVersion=0.147.0 and the environment override. The previously failing thread then archived successfully in about 300 ms; it moved to archived_sessions, disappeared from the active list, appeared in the archived list, and retained its original SHA-256.

This independently corroborates the analysis above: equivalent normal and verbatim Windows paths can enter the candidate set as distinct values before canonicalization, then resolve to the same source/destination and trigger a second rename of an already-moved file. Deduplicating by canonical file identity before constructing the move list should prevent the failure.

No raw logs, private paths, credentials, thread IDs, or conversation contents are included. Sanitized timing/log excerpts can be provided privately if useful.

gutterking0001-svg · 9 days ago

Additional Windows Desktop reproduction and impact:

  • A large local task (rollout JSONL: 275,947,041 bytes) was moved out of its project/folder in the Codex sidebar.
  • The task remained listed as notLoaded, but attempts to continue it reported thread not found.
  • Repeated archive attempts (roughly 25) failed with the same thread-store ... The system cannot find the file specified. (os error 2) error even though the rollout JSONL still existed and was readable at the cataloged path.
  • A read-only recurring monitor had also been mistakenly targeted at that stale task; deleting the monitor stopped further writes but did not repair archiving.
  • The archive failure consumed significant active work time because the stale task continued to appear active/unarchivable.

Verified recovery:

  1. Back up the local state database.
  2. Move the exact rollout JSONL from the dated sessions folder to archived_sessions.
  3. In one SQLite transaction, update only that thread row's rollout_path, set archived=1, and set archived_at.
  4. Verify the task disappears from the active list and appears in Archived Tasks.

This suggests moving a task between/out of a local project can leave the project/sidebar catalog and thread-store rollout resolution inconsistent. The archive path should reconcile an existing rollout by thread ID/basename and self-heal project-assignment metadata instead of returning os error 2. No private conversation content, credentials, or raw local paths are attached.

HaoYue1027 · 9 days ago

Comment draft for openai/codex#39130

Same issue here. Additional repro details:

  • Environment: Windows 11 (build 26200), Codex desktop 26.814.5167.0 (auto-updated from 26.810 today), CLI 0.147.0.
  • Symptom 1 (archiving): clicking "archive" in the desktop sidebar on a local thread fails with "cannot archive session" (backend thread-store os error 2).
  • Symptom 2 (opening): opening an existing thread fails with thread not found: <id> even though the .jsonl file exists on disk and the DB row is intact (archived=0).
  • Investigation: affected rows in the local state DB have rollout_path (and sometimes cwd) stored with the Windows extended-length prefix, e.g. \\?\C:\Users\Hao\.codex\sessions\.... The desktop app rewrites the path with the \\?\ prefix whenever it updates thread metadata: manually fixing the DB rows to plain paths works until the app rewrites them back.
  • Workaround that works: codex archive <thread-id> from the CLI archives those threads successfully (file moves to archived_sessions, DB row updated to archived=1).
  • Suspected root cause: archiving uses std::fs::rename and path resolution that fails on \\?\-prefixed paths on Windows (os error 2).

Please fix the \\?\-prefixed path handling (or normalize paths before rename) in the desktop archive/open code paths.

prkbll · 9 days ago

I can confirm the same behavior on the current Windows Codex Desktop app with a completed voice task: the task remains readable, but the archive request fails with the same thread-store Windows file-not-found error and the task stays visible. The task transcript/rollout file still exists locally, so moving the generated task output was not the cause. No local task-store files were modified during investigation.

marsji3945jg · 9 days ago

Validated fix against current openai/codex main (2a30972fcb646f9d6bfed6de6606aa3f0f8c3dd2) on Windows x64.

The duplicate-move bug affects both archive and the symmetric unarchive path. The minimal fix is to deduplicate rollout_moves by canonical source after scoped_rollout_path() and before rename(). This preserves the selected destination/SQLite update while ensuring one physical rollout is moved only once.

I added two real Windows regression tests:

  • archive_thread_deduplicates_windows_verbatim_sqlite_rollout_path
  • unarchive_thread_deduplicates_windows_verbatim_sqlite_rollout_path

Each test creates one physical rollout, stores its std::fs::canonicalize() result (a \\?\ verbatim path on Windows) in SQLite, and lets RolloutReferenceIndex::scan() discover the normal path spelling.

Red/green evidence:

  • Before the production change: both tests failed twice under nextest with failed to archive/unarchive thread: ... (os error 2).
  • After the change: both targeted tests passed.
  • Full just test -p codex-thread-store: 224 passed, 0 skipped.
  • just fix -p codex-thread-store: completed cleanly.
  • git diff --check: clean.
  • Rust formatting completed; the repository-wide formatter wrapper only remained nonzero because this isolated Windows environment did not have the unrelated uv and dotslash formatters.

The repository's CONTRIBUTING guide says external PRs are not accepted, so I am posting the complete tested patch here for a maintainer to apply:

diff --git a/codex-rs/thread-store/src/local/archive_thread.rs b/codex-rs/thread-store/src/local/archive_thread.rs
index b14d6ae..f956e19 100644
--- a/codex-rs/thread-store/src/local/archive_thread.rs
+++ b/codex-rs/thread-store/src/local/archive_thread.rs
@@ -9,6 +9,7 @@ use crate::ThreadStoreError;
 use crate::ThreadStoreResult;
 use chrono::Utc;
 use codex_rollout::RolloutReferenceIndex;
+use std::collections::HashSet;
 use tracing::warn;
 
 use super::thread_rollout_resolver;
@@ -85,6 +86,7 @@ async fn archive_thread_with_paths(
     }
     let mut archived_path = None;
     let mut rollout_moves = Vec::new();
+    let mut rollout_move_sources = HashSet::new();
     for rollout_path in rollout_paths {
         if rollout_path_is_archived(store.config.codex_home.as_path(), rollout_path.as_path()) {
             continue;
@@ -100,7 +102,9 @@ async fn archive_thread_with_paths(
         if rollout_path == selected_rollout_path {
             archived_path = Some(destination.clone());
         }
-        rollout_moves.push((canonical_rollout_path, destination));
+        if rollout_move_sources.insert(canonical_rollout_path.clone()) {
+            rollout_moves.push((canonical_rollout_path, destination));
+        }
     }
     let archived_path = archived_path.ok_or_else(|| ThreadStoreError::Internal {
         message: format!("failed to archive selected rollout for thread {thread_id}"),
@@ -352,4 +356,63 @@ mod tests {
         assert!(updated.archived_at.is_some());
         assert_eq!(updated.recency_at, metadata.recency_at);
     }
+
+    #[cfg(windows)]
+    #[tokio::test]
+    async fn archive_thread_deduplicates_windows_verbatim_sqlite_rollout_path() {
+        let home = TempDir::new().expect("temp dir");
+        let config = test_config(home.path());
+        let uuid = Uuid::from_u128(208);
+        let thread_id = ThreadId::from_string(&uuid.to_string()).expect("valid thread id");
+        let active_path =
+            write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file");
+        let verbatim_active_path = active_path.canonicalize().expect("canonical rollout path");
+        assert_ne!(verbatim_active_path, active_path);
+        assert!(verbatim_active_path.to_string_lossy().starts_with(r"\\?\"));
+
+        let runtime = codex_state::StateRuntime::init(
+            codex_state::SqliteConfig::new_for_testing(home.path().abs()),
+            config.default_model_provider_id.clone(),
+        )
+        .await
+        .expect("state db should initialize");
+        let store = LocalThreadStore::new(config.clone(), Some(runtime.clone()));
+        runtime
+            .mark_backfill_complete(/*last_watermark*/ None)
+            .await
+            .expect("backfill should be complete");
+        let mut builder = codex_state::ThreadMetadataBuilder::new(
+            thread_id,
+            verbatim_active_path,
+            Utc::now(),
+            SessionSource::Cli,
+        );
+        builder.model_provider = Some(config.default_model_provider_id.clone());
+        builder.cwd = home.path().to_path_buf();
+        builder.cli_version = Some("test_version".to_string());
+        let metadata = builder.build(config.default_model_provider_id.as_str());
+        runtime
+            .upsert_thread(&metadata)
+            .await
+            .expect("state db upsert should succeed");
+
+        store
+            .archive_thread(ArchiveThreadParams { thread_id })
+            .await
+            .expect("archive thread with verbatim SQLite path");
+
+        let archived_path = home
+            .path()
+            .join(ARCHIVED_SESSIONS_SUBDIR)
+            .join(active_path.file_name().expect("file name"));
+        assert!(!active_path.exists());
+        assert!(archived_path.exists());
+        let updated = runtime
+            .get_thread(thread_id)
+            .await
+            .expect("state db read should succeed")
+            .expect("thread metadata should exist");
+        assert_eq!(updated.rollout_path, archived_path);
+        assert!(updated.archived_at.is_some());
+    }
 }
diff --git a/codex-rs/thread-store/src/local/unarchive_thread.rs b/codex-rs/thread-store/src/local/unarchive_thread.rs
index 50c932f..c44491e 100644
--- a/codex-rs/thread-store/src/local/unarchive_thread.rs
+++ b/codex-rs/thread-store/src/local/unarchive_thread.rs
@@ -11,6 +11,7 @@ use crate::StoredThread;
 use crate::ThreadStoreError;
 use crate::ThreadStoreResult;
 use codex_rollout::rollout_date_parts;
+use std::collections::HashSet;
 
 use super::thread_rollout_resolver;
 use super::thread_rollout_resolver::RolloutLocation;
@@ -40,6 +41,7 @@ pub(super) async fn unarchive_thread(
     }
     let mut restored_path = None;
     let mut rollout_moves = Vec::new();
+    let mut rollout_move_sources = HashSet::new();
     for rollout_path in rollout_paths {
         if !rollout_path_is_archived(store.config.codex_home.as_path(), rollout_path.as_path()) {
             continue;
@@ -76,7 +78,9 @@ pub(super) async fn unarchive_thread(
         if rollout_path == selected_archived_path {
             restored_path = Some(destination.clone());
         }
-        rollout_moves.push((canonical_archived_path, destination));
+        if rollout_move_sources.insert(canonical_archived_path.clone()) {
+            rollout_moves.push((canonical_archived_path, destination));
+        }
     }
     let restored_path = restored_path.ok_or_else(|| ThreadStoreError::Internal {
         message: format!("failed to unarchive selected rollout for thread {thread_id}"),
@@ -271,4 +275,71 @@ mod tests {
         assert_eq!(updated.recency_at, metadata.recency_at);
         assert_eq!(updated.section, metadata.section);
     }
+
+    #[cfg(windows)]
+    #[tokio::test]
+    async fn unarchive_thread_deduplicates_windows_verbatim_sqlite_rollout_path() {
+        let home = TempDir::new().expect("temp dir");
+        let config = test_config(home.path());
+        let uuid = Uuid::from_u128(209);
+        let thread_id = ThreadId::from_string(&uuid.to_string()).expect("valid thread id");
+        let archived_path = write_archived_session_file(home.path(), "2025-01-03T13-00-00", uuid)
+            .expect("archived session file");
+        let verbatim_archived_path = archived_path
+            .canonicalize()
+            .expect("canonical rollout path");
+        assert_ne!(verbatim_archived_path, archived_path);
+        assert!(
+            verbatim_archived_path
+                .to_string_lossy()
+                .starts_with(r"\\?\")
+        );
+
+        let runtime = codex_state::StateRuntime::init(
+            codex_state::SqliteConfig::new_for_testing(home.path().abs()),
+            config.default_model_provider_id.clone(),
+        )
+        .await
+        .expect("state db should initialize");
+        let store = LocalThreadStore::new(config.clone(), Some(runtime.clone()));
+        runtime
+            .mark_backfill_complete(/*last_watermark*/ None)
+            .await
+            .expect("backfill should be complete");
+        let mut builder = codex_state::ThreadMetadataBuilder::new(
+            thread_id,
+            verbatim_archived_path,
+            Utc::now(),
+            SessionSource::Cli,
+        );
+        builder.model_provider = Some(config.default_model_provider_id.clone());
+        builder.cwd = home.path().to_path_buf();
+        builder.cli_version = Some("test_version".to_string());
+        let mut metadata = builder.build(config.default_model_provider_id.as_str());
+        metadata.archived_at = Some(metadata.updated_at);
+        runtime
+            .upsert_thread(&metadata)
+            .await
+            .expect("state db upsert should succeed");
+
+        let unarchived = store
+            .unarchive_thread(ArchiveThreadParams { thread_id })
+            .await
+            .expect("unarchive thread with verbatim SQLite path");
+
+        let restored_path = home
+            .path()
+            .join("sessions/2025/01/03")
+            .join(archived_path.file_name().expect("file name"));
+        assert!(!archived_path.exists());
+        assert!(restored_path.exists());
+        assert_eq!(unarchived.rollout_path, Some(restored_path.clone()));
+        let updated = runtime
+            .get_thread(thread_id)
+            .await
+            .expect("state db read should succeed")
+            .expect("thread metadata should exist");
+        assert_eq!(updated.rollout_path, restored_path);
+        assert_eq!(updated.archived_at, None);
+    }
 }
beifangzhishi-ops · 9 days ago

I can independently reproduce this issue on Windows Codex Desktop using the shared app-server.

After the recent update, multiple local conversations cannot be archived. The conversations remain visible in the active list with a notLoaded status, while reading them still works normally.

Sending thread/archive through the shared app-server consistently fails with:

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

The archived-conversation listing itself remains functional, and other conversations can still appear there. No local database or session files were modified during testing.

Environment:

  • Windows x64
  • Codex CLI/app-server: 0.148.0-alpha.15
  • Shared app-server over WebSockets

This appears to match the Windows archive failure described in this issue.

sichengyan10 · 9 days ago

Independent reproduction with an additional user-impact symptom.

Environment:

  • Windows build 10.0.26200.9168
  • Codex Desktop 26.814.5167.0
  • Bundled app-server/CLI 0.148.0-alpha.15
  • Local project threads

Observed:

  • Archiving repeatedly fails with: failed to archive session: thread-store internal error: failed to archive thread: The system cannot find the file specified. (os error 2).
  • The affected rollout JSONL files exist, archived_sessions exists, and the user has access to both locations.
  • Failing thread rows use a Windows verbatim rollout_path of the form \\?\C:\Users\<user>\.codex\sessions\..., while a recently successful archived thread used a normal C:\Users\<user>\... path.
  • This reproduced on both an older thread and a newly created thread in the same local project.

Additional impact not limited to the archive action:

  • Immediately after the archive attempt fails, the affected task can no longer continue the conversation in the current Codex Desktop process (the UI becomes blank/unresolvable or reports thread not found).
  • Fully restarting Codex restores the ability to continue that task.
  • After restart, archiving the same task still fails with os error 2.

No rollout, database, or project files were modified during this reproduction. This matches the normal-path/verbatim-path duplicate-move diagnosis already documented in this issue, and suggests the failed archive rollback also leaves the in-process task state unusable until restart.

aidawilliam41-ops · 8 days ago

Additional multi-thread reproduction: runtime teardown occurs before archive durability is known

Environment:

  • Windows x64
  • Codex Desktop package: 26.814.5517.0
  • App-server client_version observed in the archive logs: 26.814.41957

I reproduced the same archive failure on three unrelated completed local tasks. The new evidence concerns transaction ordering and retry behavior.

For two affected tasks:

  1. The native archive request issued Shutdown.
  2. The agent loop exited and the listener was torn down while had_active_turn=false.
  3. The request then failed with:
failed to archive session: thread-store internal error:
failed to archive thread: The system cannot find the file specified. (os error 2)
  1. An identical retry returned the same error.
  2. The task remained visible in the active list and readable, was absent from the archived list, and its matching rollout JSONL still existed.
  3. After the partial teardown, follow-up delivery could return thread not found.

A third task provided stronger cascade/hang evidence:

  • One UI archive request shut down the parent task and three associated child/subagent tasks.
  • Logs show Shutdown, agent-loop exit, and listener teardown for the parent and all three children.
  • No completion or error record followed for that archive request.
  • A second UI archive request six seconds later appeared at the message-processor layer but never reached the thread processor.
  • The parent still remained in the active list/sidebar, was absent from the archived list, and its rollout JSONL and worktree still existed.

This suggests that archive mutates live runtime state before all durable move/index prerequisites have succeeded. A failure can therefore leave a readable-but-nonmutable ghost task, and a hung archive can block a subsequent archive request.

Expected behavior:

  • Preflight parent/child storage inputs before any shutdown.
  • Make archive atomic across runtime teardown, file movement, and index state.
  • Restore active/listener state if persistence fails.
  • Make retries idempotent when the matching rollout still exists.
  • Ensure one stalled archive request cannot indefinitely block later archive requests.
  • Include the failed internal stage and a sanitized missing-object type in the error.

Evidence boundary: diagnostics were read-only. No task store, JSONL, SQLite, worktree, or Desktop state was edited. The exact missing internal object was not exposed by the current error text, so this reproduction does not independently assert the path-normalization root cause.

HaoYue1027 · 8 days ago

Additional finding on the desktop packaging side (release-pipeline issue):

Environment:

  • Windows 11 (build 26200), Codex Desktop 26.818.2441.0 (auto-updated today from 26.814.5517.0).
  • Desktop UI reports client_version 26.818.21641; archiving still fails with the same UI error on this build.

Key evidence:

  • The bundled archive core at app/resources/codex.exe (Rust binary, no version resource) contains the embedded version string 0.148.0-alpha.21, published 2026-08-17, i.e. before the dedup fix (#39256) was merged on 2026-08-18.
  • Source check: the fix (deduplicate rollout_moves by canonical source + regression tests) is present on main and in rust-v0.149.0-alpha.4, but NOT in rust-v0.149.0-alpha.1/2/3, rust-v0.148.0, or the 0.148.0-alpha.2x tags.
  • So the desktop 26.818 update bumped the MSIX/UI version, but the bundled app-server is still the pre-fix build; the archive button therefore keeps failing exactly as before.

Suggestion for the release pipeline: verify the desktop MSIX bundles an app-server binary that includes the dedup fix before shipping. Users who updated to 26.818 still hit the bug, and there is no way to tell from the MSIX version number alone.

No private paths, credentials, or conversation contents are included.

CkMovingCat · 7 days ago

Additional causal isolation from Codex Desktop on Windows.

Environment

  • Codex Desktop package: 26.814.5517.0
  • Bundled app-server/CLI: 0.148.0-alpha.15
  • Windows x64
  • Local NTFS storage

Isolated protocol experiment

I ran the signed bundled app-server against disposable, isolated state stores. No model turn was started and no real conversation data was used.

A 2x2 creation matrix varied both CODEX_HOME and the requested project cwd between ordinary and Windows verbatim path spellings:

  1. ordinary home + ordinary cwd
  2. verbatim home + ordinary cwd
  3. ordinary home + verbatim cwd
  4. verbatim home + verbatim cwd

In all four cases:

  • thread/start plus thread/name/set persisted an ordinary, non-verbatim rollout_path;
  • the stored cwd was canonicalized to a verbatim path, but this did not affect archival;
  • the rollout JSONL existed.

I then used a fresh isolated thread and inspected the same SQLite row after each app-server operation:

thread/start + thread/name/set -> ordinary rollout_path
thread/list                    -> unchanged
thread/read                    -> unchanged
thread/read includeTurns=true  -> unchanged
thread/resume                  -> rollout_path rewritten to \\?\C:\...
thread/archive                 -> -32603 / os error 2

A same-environment control thread that was archived directly, without thread/resume, succeeded:

  • response had no error;
  • archived=1;
  • the rollout moved to archived_sessions;
  • the original active-session path disappeared.

This establishes that, in this build, the verbatim selected rollout path is not introduced by initial thread creation, AI-vs-user creation, the project cwd spelling, or the CODEX_HOME spelling. The direct upstream writer is thread/resume.

This complements the duplicate canonical-source move analysis already documented above. A regression test should cover the complete lifecycle: create with an ordinary stored rollout path, resume by thread ID, verify the persisted path representation, then archive exactly once.

No thread IDs, user paths, credentials, logs, databases, rollout contents, or project contents are included.

hewzhew · 7 days ago

Current-build reproduction: failed archive rolls back the file move but not the live thread teardown

I reproduced this on the current Windows Desktop package and found a second failure boundary after the duplicate-move / verbatim-path archive error already documented here.

Environment
  • Codex Desktop MSIX: 26.818.2441.0 (Windows x64)
  • Desktop client release in logs: 26.818.21641
  • Embedded app-server/runtime: 0.148.0
  • Local legacy thread, gpt-5.6-sol, xhigh
  • Persisted rollout_path used the Windows verbatim form \\?\C:\...
Sanitized sequence
T+0.0s   active turn is interrupted successfully
T+7.5s   Archive is requested from the sidebar context menu
T+7.6s   app-server clears the thread listener during teardown
         had_listener=true, had_active_turn=true
T+8.5s   thread/archive returns JSON-RPC -32603:
         failed to archive thread: The system cannot find the file. (os error 2)
T+11s    the Desktop route/listener becomes active again for the still-visible thread
T+29s    thread/settings/update returns -32600: thread not found
T+32s    turn/start returns -32600: thread not found
         Composer submit fails

The archive action was attempted a second time from the same sidebar menu and failed identically.

State after failure
  • The rollout JSONL still existed under the active sessions tree and was readable.
  • Immediately before the archive attempt, the rollout had resumed with 7,571 items and parse errors: 0.
  • The SQLite thread row still existed with archived=0 and the active rollout path.
  • No archived copy was left behind.
  • The Desktop UI continued to display and reopen the thread route, but the app-server no longer had a resolvable live thread instance.

This is consistent with the canonical-source duplicate move described above: the filesystem portion restores the earlier move when the second rename fails. However, the runtime teardown is not restored with it. The failed archive therefore converts an otherwise readable thread into a visible-but-dead thread until recovery/restart.

Suggested invariant

thread/archive should be transactional across both storage and runtime state:

  1. do not tear down the listener/session until the archive commit succeeds; or
  2. if storage archive fails and rollout moves are restored, also restore/resume the thread listener and runtime instance;
  3. the Desktop should not reactivate a route that cannot accept thread/settings/update or turn/start—it should explicitly resume/reconcile it first.

A regression test could force the duplicate-source second-rename failure, then assert that the same thread can still accept a settings update or a new turn after thread/archive returns the error.

No private paths, thread/turn IDs, task titles, prompts, account data, conversation contents, screenshots, or raw logs are included.

philip-hawz · 7 days ago

Additional population-level and post-update evidence from Windows Codex Desktop:

Environment

  • Microsoft Store package: OpenAI.Codex 26.818.2441.0
  • Desktop release reported in logs: 26.818.21641
  • Windows x64, local NTFS storage

Impact and recurrence

A read-only census taken before repair found:

  • 104 active thread rows
  • 22 active rows whose threads.rollout_path began with the Windows verbatim prefix \\?\
  • the rollout JSONL existed at the equivalent ordinary Windows path for 21 of the 22 affected rows

Across ten independent SQLite backup snapshots for that same 22-thread cohort:

  • 17 paths transitioned from ordinary to verbatim form
  • 1 path transitioned verbatim -> ordinary -> verbatim
  • 4 were verbatim in every available snapshot

This shows that the prefix is not only legacy data; it is re-persisted later in the thread lifecycle.

Failure after the 26.818 update

Two distinct completed local threads failed on 2026-08-20 at 10:57:30Z and 11:47:57Z. Both requests returned:

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

The source rollouts existed and remained readable.

A narrow startup guard was already active: it only normalizes the exact startup session's one rollout_path row after creating an independent SQLite backup, verifies the result, and never archives automatically. The later failures still occurred, confirming that startup-only normalization cannot prevent the running app from reintroducing the verbatim path (consistent with the thread/resume evidence already reported here).

For each affected thread, changing only rollout_path from the verbatim form to the equivalent ordinary path, then using the official archive command, succeeded. Post-archive verification found the database row and archived rollout consistent, with no missing or mismatched files.

This is additional evidence that Desktop 26.818.2441.0 still ships an archive core without the deduplication/path-normalization fix. Please include the existing Windows archive/unarchive regression fix in the bundled Desktop app-server and test the full lifecycle:

startup -> thread/resume -> persisted rollout_path -> thread/archive -> sidebar refresh

No thread IDs, user paths, account data, rollout contents, databases, or raw logs are included.

DingJH · 7 days ago

Independent confirmation from another Windows Codex Desktop installation.

Environment

  • Microsoft Store package: OpenAI.Codex 26.818.2441.0
  • Desktop client release: 26.818.21641
  • Affected rows record bundled runtime/CLI 0.148.0-alpha.15
  • Windows x64, local session store

Population-level evidence

A read-only census found:

  • 112 unarchived ordinary user-thread rows
  • 42 rows whose threads.rollout_path used the Windows verbatim prefix \\?\
  • all 42 had an existing, readable rollout JSONL at the equivalent ordinary C:\... path
  • no active rollout files were missing

Attempting to archive one affected completed thread returned:

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

After creating a consistent SQLite backup and changing only that row's rollout_path from the verbatim spelling to the equivalent ordinary spelling, the official archive operation succeeded immediately. The thread appeared in the archived list and disappeared from the recent list.

A guarded bulk normalization then changed all 42 affected rows, with zero missing files afterward. During the still-active diagnostic thread, the app persisted that active row back to the \\?\C:\... form, leaving one affected row again. This independently confirms that normalization is only a workaround: the running Desktop lifecycle can reintroduce the incompatible path representation, so newly created/resumed threads are not guaranteed to remain archivable.

Please fix the archive candidate deduplication/path identity handling and ensure thread/resume does not persist a path spelling that thread/archive cannot process.

No user name, thread IDs, task titles, conversation content, private paths, databases, or raw logs are included.

HaoYue1027 · 7 days ago

Confirmed fixed in the shipped desktop build.

Environment:

  • Windows 11 (build 26200), Codex Desktop 26.818.3698.0 (Microsoft Store, auto-updated).
  • Bundled app-server 0.149.0 (verified via the embedded version string in app/resources/codex.exe).

Verification:

  • Archiving local threads from the desktop sidebar now succeeds on this build (the same threads failed with os error 2 on 26.814.x and on 26.818.2441.0).
  • Source check: the dedup fix (#39256) is present in the rust-v0.149.0 tag — archive_thread.rs deduplicates rollout_moves by canonical source, and the regression test archive_thread_deduplicates_rollout_paths is included.
  • Packaging note for the pipeline: 26.818.2441.0 still bundled 0.148.0-alpha.21 (pre-fix), which is why the earlier 26.818 update did not resolve the issue; 26.818.3698.0 bundles 0.149.0 (post-fix).

No private paths or conversation contents are included. Thanks for the fix — this issue can likely be closed.