move-codex-session rollback keeps copied history rows when copyHistory throws after its commit #140

Open
opened 2026-10-10 03:57:33 +00:00 by jercik · 0 comments
Owner

If the history copy commits but copyHistory then throws, the copied history rows stay in the destination, and the rollback finishes without reporting them. The state and thread databases follow the same pattern.

At 1a85c77, main sets historyCopied = true only after copyHistory(...) returns (lines 2159-2160). copyHistory commits at line 1220, then closes both connections in a finally (lines 1223-1226). If anything throws after the commit, such as an I/O error or close(), the flag is still false.

cleanupDestination then skips the history ownership check (line 1901) and passes no history ids to deleteDestinationDatabases (line 1952, historyCopied ? historyIds : []). The rollouts and index entries are rolled back, the cleanup returns without throwing, and main rethrows only the original error. It gives no destination rollback failed message naming the history database.

Before #133, the rollback always passed historyIds. That covered this case but deleted history rows the script never copied, which #133 fixed by gating on the flag.

By the code, a rerun is then refused by assertDestinationEmpty (lines 911-917, "destination ... already contains history for ...") until someone removes the rows by hand.

The same gap applies to stateCopied (lines 2157-2158) and to copiedThreadDatabasePrefixes (lines 2161-2164), which are also set only after copyState and copyThreadDatabase return.

Evidence: a hypothesis from reading the code. Not reproduced. Confirming it needs a fault injected after the commit, for example a failing close().

If the history copy commits but `copyHistory` then throws, the copied history rows stay in the destination, and the rollback finishes without reporting them. The state and thread databases follow the same pattern. At `1a85c77`, `main` sets `historyCopied = true` only after `copyHistory(...)` returns (lines 2159-2160). [`copyHistory`](https://code.j4k.dev/j4k-oss/agent-skills/src/commit/1a85c77277b4f8924278686aa2c2969477c4eece/skills/move-codex-session/scripts/move-codex-session.ts#L1196-L1227) commits at line 1220, then closes both connections in a `finally` (lines 1223-1226). If anything throws after the commit, such as an I/O error or `close()`, the flag is still `false`. [`cleanupDestination`](https://code.j4k.dev/j4k-oss/agent-skills/src/commit/1a85c77277b4f8924278686aa2c2969477c4eece/skills/move-codex-session/scripts/move-codex-session.ts#L1873-L1980) then skips the history ownership check (line 1901) and passes no history ids to `deleteDestinationDatabases` (line 1952, `historyCopied ? historyIds : []`). The rollouts and index entries are rolled back, the cleanup returns without throwing, and `main` rethrows only the original error. It gives no `destination rollback failed` message naming the history database. Before #133, the rollback always passed `historyIds`. That covered this case but deleted history rows the script never copied, which #133 fixed by gating on the flag. By the code, a rerun is then refused by `assertDestinationEmpty` (lines 911-917, "destination ... already contains history for ...") until someone removes the rows by hand. The same gap applies to `stateCopied` (lines 2157-2158) and to `copiedThreadDatabasePrefixes` (lines 2161-2164), which are also set only after `copyState` and `copyThreadDatabase` return. Evidence: a hypothesis from reading the code. Not reproduced. Confirming it needs a fault injected after the commit, for example a failing `close()`.
Sign in to join this conversation.
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
j4k-oss/agent-skills#140
No description provided.