You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug(ghosts): after a reload, a semantic replay re-mints ghosts of already-committed blocks #1233
Found in the #1228 review (reviewer A). This is a different route to the #730 duplicate, and it predates that PR.
A ghost is superseded and persisted.
The app reloads. readGhostLog drops superseded ghosts (src/main/ghostJournal.ts:265-271), so the renderer has no marker for that uuid.
The committed history loads.
A semantic replay opens the same turn and block. Codex rollout replay leaves semantic.currentTurn open (useIpcSubscriptions.ts near 2718).
ghostsFromSemanticTurn sees an empty slot and mints a fresh, unsuperseded ghost. The semantic handler never reconciles it against the runtime.entries already loaded.
30 s after the turn clears, it orphans with an updatedAt newer than the old tail, and selectMergedEntries paints a duplicate.
Evidence: a temporary reproduction with the #1228 fixture's first 4 records gave a reduceGhostLogSansSuperseded size of 0, and then painted both the committed row and the ghost.
Evidence still needed: a recorded reload in which the replay actually re-opens a committed turn (Codex rollout replay is the suspect).
Fix direction: reconcile newly minted ghosts against the committed entries already held, for example by message.id, codexTurnId or tool-use id through the existing indexes. Alternatively, keep superseded markers in the log reduction for the current tail.
Re-audit after #1235 (quality loop, 2026-09-25): steps 1–2 of the sequence no longer exist. #1235 removed the on-disk ghost log (src/main/ghostJournal.ts, readGhostLog are gone from main), so there are no persisted superseded ghosts to drop across a reload.
What remains is narrower. After a reload, if a semantic replay re-opens a turn whose entries are ALREADY loaded, ghostsFromSemanticTurn mints a ghost that nothing supersedes (reconcileUpstream reconciles newly arriving entries). After the orphan TTL it could paint a duplicate.
That still needs a recorded reload in which a replay actually re-opens a committed turn; Codex rollout replay is the suspect. Keeping this needs-evidence.
Activity
Juliusolsson05 commentedon Sep 25, 2026
Re-audit after #1235 (quality loop, 2026-09-25): steps 1–2 of the sequence no longer exist. #1235 removed the on-disk ghost log (
src/main/ghostJournal.ts,readGhostLogare gone from main), so there are no persisted superseded ghosts to drop across a reload.What remains is narrower. After a reload, if a semantic replay re-opens a turn whose entries are ALREADY loaded,
ghostsFromSemanticTurnmints a ghost that nothing supersedes (reconcileUpstreamreconciles newly arriving entries). After the orphan TTL it could paint a duplicate.That still needs a recorded reload in which a replay actually re-opens a committed turn; Codex rollout replay is the suspect. Keeping this needs-evidence.