codex - 💡(How to fix) Fix Desktop: thread.archived silently reset to 0 on next app cycle for any thread whose sessions/<rollout>.jsonl exists

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Root Cause

  • The archive feature is silently non-functional for users with non-trivial accumulated history. A user who relies on the archive feature to manage sidebar load will see their archives un-do themselves and re-bloat the sidebar.

  • For automated / scripted bulk-archive workflows (operators on multi-agent systems, IT-managed Codex profiles, profile cleanup scripts), the archive operation is structurally unreliable — there is no API or DB-level write that survives the next reconciliation cycle for any thread with retained session data.

  • This compounds with the GUI 30s backfill timeout issue (#23787) on profiles with large session corpora: such profiles are exactly the ones where archive is most needed, and exactly the ones where archive is silently reverted.

Fix Action

Fix / Workaround

Workaround for affected users

None currently survives reconciliation. Possible mitigations:

I would prefer not to delete session jsonl files; they are historical record. The fix belongs in the reconciliation logic, not in a user workaround.

Code Example

BEGIN IMMEDIATE;
UPDATE threads
   SET archived = 1,
       archived_at = ?  -- current unix ms
 WHERE archived = 0
   AND updated_at_ms < ?;  -- now - 5 days in ms
-- UPDATE affected 121 rows
COMMIT;

---

archived=140   (back to pre-UPDATE baseline)
archived=0183  (back to pre-UPDATE baseline)
archived_at IS NOT NULL40   (back to pre-UPDATE baseline)
archived=0 AND archived_at NOT NULL0    (no "flag-flipped-only" rows)

---

Threads I archived (matched by ID against pre-UPDATE backup):       121
Of those — live state archived=1:                                     0
Of those — live state archived=0:                                   121
Of those — rollout_path file exists at ~/.codex/sessions/...:       121
Of those — rollout_path file missing on disk:                         0

---

ALTER TABLE threads ADD COLUMN preview TEXT NOT NULL DEFAULT '';

  UPDATE threads
  SET preview = first_user_message
  WHERE preview = '' AND first_user_message <> '';

  UPDATE threads
  SET preview = (
      SELECT thread_goals.objective
      FROM thread_goals
      WHERE thread_goals.thread_id = threads.id
  )
  WHERE preview = ''
      AND EXISTS (
          SELECT 1 FROM thread_goals
          WHERE thread_goals.thread_id = threads.id
              AND thread_goals.objective <> ''
      );

---

sqlite3 ~/.codex/state_5.sqlite "
     UPDATE threads
        SET archived = 1, archived_at = $(date +%s%3N)
      WHERE archived = 0
        AND updated_at_ms < $(($(date +%s%3N) - 5*24*60*60*1000));
   "
   sqlite3 ~/.codex/state_5.sqlite "PRAGMA integrity_check;"  # should print: ok
   sqlite3 ~/.codex/state_5.sqlite "SELECT COUNT(*) FROM threads WHERE archived=1;"

---

sqlite3 ~/.codex/state_5.sqlite "SELECT COUNT(*) FROM threads WHERE archived=1;"
   sqlite3 ~/.codex/state_5.sqlite "SELECT COUNT(*) FROM threads WHERE archived=0 AND archived_at IS NOT NULL;"
RAW_BUFFERClick to expand / collapse

What version of the Codex App are you using (From "About Codex" dialog)?

26.519.2081.0

What subscription do you have?

Pro

What platform is your computer?

Microsoft Windows NT 10.0.26200.0 x64 (Windows native binary path; not WSL backend)

What issue are you seeing?

threads.archived is silently reset from 1 back to 0 (and threads.archived_at is cleared from a populated timestamp back to NULL) for any thread whose sessions/<dated>/rollout-*.jsonl file still exists on disk, on the next Codex Desktop session-reconciliation cycle after a bulk archive performed via direct UPDATE on state_5.sqlite.

This is not a UI display issue. The archive flag itself on the row is reverted by the backend. The thread reappears in the active sidebar at the next sync. _sqlx_migrations is unaffected; PRAGMA integrity_check returns ok; only threads.archived and threads.archived_at are mutated.

Deterministic evidence (this is a clean repro, not a flaky one)

I bulk-archived 121 threads via a single atomic transaction:

BEGIN IMMEDIATE;
UPDATE threads
   SET archived = 1,
       archived_at = ?  -- current unix ms
 WHERE archived = 0
   AND updated_at_ms < ?;  -- now - 5 days in ms
-- UPDATE affected 121 rows
COMMIT;

Immediate post-COMMIT SELECT verified active=62, archived=161 (was active=183, archived=40 before). PRAGMA integrity_check returned ok. A bit-for-bit backup of state_5.sqlite taken right after the COMMIT also shows archived=161.

I then closed Codex, did unrelated work for ~70 minutes during which Codex ran normally (started/stopped a few times, applied migrations, ran reconciliation), and re-queried state_5.sqlite:

archived=1                          → 40   (back to pre-UPDATE baseline)
archived=0                          → 183  (back to pre-UPDATE baseline)
archived_at IS NOT NULL             → 40   (back to pre-UPDATE baseline)
archived=0 AND archived_at NOT NULL → 0    (no "flag-flipped-only" rows)

The 121 archive events were fully wiped — both the archived flag AND the archived_at timestamp were restored. Compared file hashes between the post-archive backup and the current live state_5.sqlite: hashes differ (file is not a backup restore — content has otherwise advanced normally), but the archive-related fields are exactly equal to the pre-archive state for those 121 rows.

Perfect correlation with session-file presence

I extracted the set of 121 thread IDs that were targeted by my UPDATE (from the pre-UPDATE backup) and checked their rollout_path value against the actual filesystem:

Threads I archived (matched by ID against pre-UPDATE backup):       121
Of those — live state archived=1:                                     0
Of those — live state archived=0:                                   121
Of those — rollout_path file exists at ~/.codex/sessions/...:       121
Of those — rollout_path file missing on disk:                         0

121 / 121 / 121. Every single thread I archived had its session jsonl file still present on disk, and every single one was un-archived by the next reconciliation cycle.

What this is NOT

  • Not a v32 threads_preview migration side effect. I extracted the bundled migration SQL from the binary's .rodata section (it lives just after the migration string at offset ~185452328 in resources/codex.exe for this release). The migration body is:

    ALTER TABLE threads ADD COLUMN preview TEXT NOT NULL DEFAULT '';
    
    UPDATE threads
    SET preview = first_user_message
    WHERE preview = '' AND first_user_message <> '';
    
    UPDATE threads
    SET preview = (
        SELECT thread_goals.objective
        FROM thread_goals
        WHERE thread_goals.thread_id = threads.id
    )
    WHERE preview = ''
        AND EXISTS (
            SELECT 1 FROM thread_goals
            WHERE thread_goals.thread_id = threads.id
                AND thread_goals.objective <> ''
        );

    The migration only writes to preview. It does not touch archived or archived_at. Confirmed independently.

  • Not a SQLx CRLF/LF checksum-drift recurrence (#23777 / #23787). _sqlx_migrations is intact; integrity_check is ok; baseline verifier passes.

  • Not a Phase 1 / WAL-replay edge case. I confirmed the archive COMMIT landed in the main DB file (not just the WAL) via a post-commit sqlite3 open after Codex was fully stopped, before any subsequent restart.

  • Not UI-only. The archived=0 and archived_at IS NULL mutation is visible at the SQL level when querying state_5.sqlite directly. It is the backend that is performing the reversion.

Root-cause hypothesis (testable)

The Codex Desktop backend appears to run a session-reconciliation pass that iterates ~/.codex/sessions/<year>/<month>/<day>/rollout-*.jsonl and, for any thread whose rollout_path matches a present file, force-sets archived=0 and clears archived_at — treating "session jsonl exists on disk" as evidence-of-active that should override any persisted archive state.

If correct, this is a policy bug, not a code bug per se: the policy treats session-on-disk as primary truth and discards the row's own archived state, even when that state was explicitly written by the user (or by a tool the user authorized).

What steps can reproduce the bug?

  1. Open Codex Desktop on a profile with at least ~50 threads that are >5 days old (i.e., realistic accumulated state). Confirm in the UI sidebar that they are listed as Active.

  2. Close Codex Desktop completely (Get-Process Codex,codex | Stop-Process -Force on Windows; ensure no Codex backend processes remain).

  3. Confirm ~/.codex/state_5.sqlite is unlocked: no -shm / -wal siblings, or -wal is 0 bytes / empty.

  4. Run a direct DB archive:

    sqlite3 ~/.codex/state_5.sqlite "
      UPDATE threads
         SET archived = 1, archived_at = $(date +%s%3N)
       WHERE archived = 0
         AND updated_at_ms < $(($(date +%s%3N) - 5*24*60*60*1000));
    "
    sqlite3 ~/.codex/state_5.sqlite "PRAGMA integrity_check;"  # should print: ok
    sqlite3 ~/.codex/state_5.sqlite "SELECT COUNT(*) FROM threads WHERE archived=1;"

    Record the post-UPDATE archived count.

  5. Relaunch Codex Desktop. Let it complete startup including any backfill / reconciliation passes (give it ~2 minutes on a non-trivial session corpus).

  6. Re-query state_5.sqlite:

    sqlite3 ~/.codex/state_5.sqlite "SELECT COUNT(*) FROM threads WHERE archived=1;"
    sqlite3 ~/.codex/state_5.sqlite "SELECT COUNT(*) FROM threads WHERE archived=0 AND archived_at IS NOT NULL;"

    Expected (current actual behavior): archived count is back near pre-UPDATE baseline; the archived=0 AND archived_at NOT NULL "smoking gun" count is 0 (the timestamp is cleared along with the flag).

What is the expected behavior?

The user's persisted archive state should be respected. Either:

Fix A (preferred): Treat archived_at IS NOT NULL as user-intent and skip the un-archive reconciliation step for those rows. Sessions can still exist on disk for legitimate reasons (history retention, search) without implying the thread should be active.

Fix B: Reconciliation un-archives a row only when a newly arrived session jsonl is detected (file mtime > thread.archived_at), not on every reconciliation cycle.

Fix C: Remove the un-archive-from-session-presence behavior entirely and rely only on explicit user action (clicking Unarchive in the UI) to flip archived=0. If a session file exists on disk for an archived thread, that's fine — the thread is archived; the session jsonl is historical.

Why this matters

  • The archive feature is silently non-functional for users with non-trivial accumulated history. A user who relies on the archive feature to manage sidebar load will see their archives un-do themselves and re-bloat the sidebar.

  • For automated / scripted bulk-archive workflows (operators on multi-agent systems, IT-managed Codex profiles, profile cleanup scripts), the archive operation is structurally unreliable — there is no API or DB-level write that survives the next reconciliation cycle for any thread with retained session data.

  • This compounds with the GUI 30s backfill timeout issue (#23787) on profiles with large session corpora: such profiles are exactly the ones where archive is most needed, and exactly the ones where archive is silently reverted.

Additional information

Suggested testing on the Codex side

If maintainers want to verify the hypothesis without my repro:

  1. Pick any archived thread T in a test profile.
  2. Confirm T's rollout_path file exists at ~/.codex/sessions/.../rollout-*.jsonl.
  3. Restart Codex Desktop.
  4. Re-check T's archived value in state_5.sqlite post-reconciliation.

If T flipped from archived=1 to archived=0 without any user UI interaction, the reconciliation pass is the source.

Related issues that are not this one

  • #23777 (CRLF/LF SQLx migration checksum drift) — different bug; involves _sqlx_migrations.checksum, not threads.archived.
  • #23787 (logs_2.sqlite migrations modified in place + 30s GUI backfill cap) — different bug; backfill ≠ session-reconciliation.
  • #17354 (Recent thread history wiped in app, present in CLI) — opposite direction; this issue is "history that should-be-archived is silently un-archived."
  • #19742 (Codex Desktop automation run is immediately auto-archived after start) — opposite direction; automation-specific.

Workaround for affected users

None currently survives reconciliation. Possible mitigations:

  • Move corresponding ~/.codex/sessions/<...>.jsonl files out of the sessions tree before archiving (destructive to history).
  • Use only the UI Archive feature (which presumably handles reconciliation correctly — untested by me).
  • Wait for upstream fix.

I would prefer not to delete session jsonl files; they are historical record. The fix belongs in the reconciliation logic, not in a user workaround.

Reproducibility

The 121/121 correlation in my data is deterministic. If maintainers want the anonymized thread-ID set + their rollout_path values for verification, I can provide.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

codex - 💡(How to fix) Fix Desktop: thread.archived silently reset to 0 on next app cycle for any thread whose sessions/<rollout>.jsonl exists