I’m also experiencing the same issue.
Environment: Windows 10 — ChatGPT Windows Desktop package OpenAI.Codex_26.903.9818.0_x64__2p2nqsd0c76g0
Diagnostic method: Investigated using ChatGPT Work (GPT-6 Astra Light) in strictly read-only mode, together with GPT-5.6 Sol for analysis and test planning. Work inspected the local SQLite database/WAL, desktop logs, and installed desktop implementation without modifying application state.
ChatGPT Windows Desktop — Deleted conversations persist as ghost catalog entries
Summary
Conversations deleted successfully on ChatGPT web can remain indefinitely visible in the Windows desktop app as undeletable “ghost” conversations.
I currently have 7 confirmed ghost entries: some within an existing Project and others in Recents. The Project itself still exists; only its conversations were deleted.
User-visible behavior
A conversation deleted on ChatGPT web remains visible in the Windows desktop sidebar.
Opening a tested ghost produces:
Could not load this ChatGPT conversation.
Logs show the server returning HTTP 404 including:
-
conversation_deleted
-
can_retry=false
Attempting to delete that already-deleted conversation through the desktop app also returns HTTP 404 and leaves the ghost entry intact.
A complete uninstall/reinstall and removal of local application data did not resolve the issue.
Local catalog evidence
The desktop client maintains:
%USERPROFILE%\.codex\sqlite\codex-dev.db
Table:
local_thread_catalog
All 7 known ghosts remain active with:
source_kind = chatgpt
missing_candidate = 0
Strong evidence from incremental synchronization
Historical committed SQLite WAL states preserved catalog scan checkpoints.
Three recovered incremental scan sequences contained 72 response-derived conversation IDs and zero of the 7 known ghost IDs.
Most significantly, the latest recovered set of 72 IDs equals exactly:
79 locally active ChatGPT catalog rows − 7 known ghosts = 72 response-derived IDs
The installed implementation constructs these checkpoint IDs from items returned by:
/conversations?offset=0&limit=100&order=updated&is_archived=false&hide_snorlax=false
rather than from existing local catalog rows.
For one recovered scan, WAL history shows:
-
Incremental checkpoint initialized.
-
72 response-derived IDs saved; none are ghost IDs.
-
Scan reaches its reconciliation phase.
-
Scan completes successfully.
-
Checkpoint is cleared.
-
Catalog watermark advances.
-
All 7 absent local rows remain active with missing_candidate = 0.
This strongly supports the conclusion that incremental synchronization can successfully receive a conversation set excluding the deleted conversations while failing to retire their stale local rows.
Relevant implementation behavior
Inspection of the installed desktop implementation indicates:
Incremental scans
Incremental completion does not perform missing-entry reconciliation.
Therefore, a locally active conversation absent from an incremental server result can remain active.
Full scans
Missing-entry processing occurs during full scans.
An eligible active row absent from a completed full scan can first become:
missing_candidate = 1
A subsequent full scan can remove a previously missing row if it remains absent and sequence conditions permit.
Full reconciliation scheduling
The inspected eligibility logic effectively requires a full reconciliation when:
An existing full-reconciliation timestamp does not appear to expire based on age.
Ordinary startup/refresh/notification-driven synchronization appears to use incremental mode once initial population has completed.
A retry mechanism exists for an already-required full scan, and migration code can clear the full-reconciliation timestamp, but no periodic age-based full reconciliation was found in the inspected catalog code.
During observation, catalog synchronization continued and its observation sequence advanced, while the previous full-reconciliation timestamp remained unchanged and all 7 ghosts stayed missing_candidate = 0.
Deleted-conversation error handling
The inspected conversation-load failure path propagates/logs HTTP 404 conversation_deleted but does not appear to mark or remove the corresponding local catalog row.
The desktop delete sequence also appears to:
-
request server-side deletion;
-
then remove/notify the local catalog after success.
If the server responds that the conversation is already deleted, the 404 occurs before local catalog cleanup.
Thus an already-deleted ghost cannot repair itself through the normal desktop delete operation.
Likely failure mechanism
The evidence supports this sequence:
-
Conversation exists and enters the desktop catalog.
-
Conversation is deleted through ChatGPT web or another client.
-
Later incremental /conversations synchronization no longer returns its ID.
-
Incremental reconciliation does not process locally active rows absent from the result.
-
The stale row remains missing_candidate = 0.
-
No periodic full reconciliation occurs to discover the missing row.
-
The sidebar continues displaying the ghost.
-
Opening it returns HTTP 404 conversation_deleted.
-
That error path does not retire the local row.
-
Desktop deletion also receives 404 before local cleanup.
Expected behavior
A conversation authoritatively deleted elsewhere should eventually disappear from the Windows desktop catalog/sidebar.
Additionally, HTTP 404 conversation_deleted could reasonably be treated as authoritative confirmation that the local catalog entry should be retired. Deleting an already-deleted conversation could effectively be handled as an idempotent success for local cleanup purposes.
Diagnostic limitations
The recovered checkpoint data contains parsed conversation IDs/update timestamps rather than raw HTTP response bodies.
Therefore this does not prove every possible server response condition. However, three recovered incremental scans contained zero known ghost IDs, and the latest recovered set contained exactly all locally active non-ghost IDs while the 7 additional ghost rows remained active.
All ghost entries have been intentionally preserved in their current state in case further diagnostic testing is useful.