fix: delete partial SESSION_LOGS objects when a put fails - #123
Conversation
Wait for all three archive puts to settle. If any put fails, delete the attempted events, transcript, and summary keys before rethrowing so a unique-prefix retry cannot leave untracked R2 objects. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: needs real behavior proof before merge. Reviewed September 1, 2026, 8:06 AM ET / 12:06 UTC. ClawSweeper reviewWhat this changesThis PR waits for all three interactive-session archive uploads, deletes the attempted R2 keys after any failure, and adds a regression test for a failed summary upload. Merge readiness⛔ Blocked until real behavior proof from a real setup is added - 4 items remain Keep open: the cleanup approach is useful, but it still rethrows by upload-array order rather than the earliest failed upload, and the supplied after-fix evidence uses a Map-backed substitute rather than a Worker R2 binding. Priority: P2 Review scores
Verification
How this fits togetherThe Worker turns interactive-session rows and events from D1 into three optional SESSION_LOGS R2 archive objects, then records their keys in D1. Session lifecycle paths call this archive function, so upload failures must leave no untracked objects and preserve useful operator diagnostics. flowchart LR
A[Session lifecycle event] --> B[Worker archive service]
B --> C[Read D1 session and events]
C --> D[Write three R2 archive objects]
D --> E{All uploads succeed?}
E -->|Yes| F[Store archive keys in D1]
E -->|No| G[Delete attempted R2 keys]
G --> H[Return upload error]
Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Capture the first rejection as uploads settle, retain the all-settled cleanup fence, add a staged dual-failure regression, and provide redacted Worker/R2 after-fix output before merge. Do we have a high-confidence way to reproduce the issue? Yes, source establishes the failure path: two staged rejected upload promises make the introduced array-order selection return the wrong error after cleanup. Is this the best way to solve the issue? No: waiting for all uploads before cleanup is the right fence, but the PR must record the first temporal rejection rather than select the first rejected array element. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against bdd5083b0d3d. LabelsLabel changes:
Label justifications:
EvidenceWhat I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (15 earlier review cycles; latest 8 shown)
|
What Problem This Solves
Fixes an issue where operators who archive interactive session logs to SESSION_LOGS would leave untracked R2 objects behind when one of the three archive puts failed. Session event append, attach, metadata, stop, and terminal finalize all call
archiveInteractiveSessionLogs. That function writes events, transcript, and summary under a uniquecrypto.randomUUID()prefix, then inserts the archive row. If a later put fails, the earlier objects stay in the bucket with no database row. A retry uses a new prefix, so those orphans are never reclaimed.Why This Change Was Made
Wait for every archive put to finish, then delete the attempted events, transcript, and summary keys before rethrowing the original put error. Existing
cleanupSessionLogArchiveObjectsalready knows how to delete that key triple. The database insert still happens only after all three puts succeed.User Impact
A failed session-log archive attempt no longer leaves untracked SESSION_LOGS objects. Operators keep the original put error, and a later successful archive still writes a fresh unique prefix. Successful archives are unchanged.
Evidence
Before this change, a SESSION_LOGS bucket that accepted events and transcript then rejected summary left two objects and deleted nothing:
After the patch, the same failing summary put still raises
R2 put failed, but the production archive path deletes all three attempted keys and leaves an empty store:Real behavior proof
Behavior or issue addressed: A later SESSION_LOGS put failure left earlier unique-key archive objects with no database row and no cleanup.
Real environment tested: macOS Darwin 25.6.0 arm64, Node v26.7.0, crabfleet checkout
/tmp/oc-pr-crabfleet-F004at branchfix/f004-session-logs-cleanupon top ofbdd5083.Exact steps or command run after this patch:
Evidence after fix: terminal output from the patched archive path above. The failing summary put still surfaces
R2 put failed. The leftover store is empty, and delete ran for events, transcript, and summary under the same unique attempt prefix.Observed result after fix:
archiveInteractiveSessionLogsno longer keeps untracked SESSION_LOGS objects after a partial put failure. The original put error is still thrown, and no archive row is inserted.What was not tested: A live Cloudflare R2 bucket and a production worker process with real session traffic.
Summary
The leak has been present since
df9bdc99in #44 (2026-06-15, 75 days). Event content later changed in #76, but the three-wayPromise.allput still had no failure cleanup.Related:
cleanupSessionLogArchiveObjectsalready deletes obsolete keys after a successful commit. This change uses that helper when the put set fails, afterPromise.allSettledso an in-flight sibling put cannot land after cleanup starts.