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 August 31, 2026, 1:32 AM ET / 05:32 UTC. ClawSweeper reviewWhat this changesThis PR waits for all three interactive-session R2 archive uploads, deletes the attempted objects after any failure, and adds regression coverage for a failed archive write. Regression provenancePossible regression — suspected (reviewed change). No predecessor PR is attributed. Merge readiness⛔ Blocked until real behavior proof from a real setup is added - 6 items remain Keep open: the cleanup approach addresses the orphan-object leak, but the branch changes failure selection from the earliest rejected R2 upload to the first upload position, and its supplied proof is mock-only. Priority: P2 Review scores
Verification
How this fits togetherThe Worker periodically serializes interactive-session events into three R2 snapshot objects and stores their committed pointers in D1. Session lifecycle operations invoke this archive path, so a failed upload must leave neither a usable D1 pointer nor untracked objects. flowchart LR
A[Session lifecycle event] --> B[Archive session snapshot]
B --> C[Three R2 uploads]
C --> D{All uploads succeed?}
D -->|Yes| E[Store D1 archive pointers]
D -->|No| F[Delete attempted R2 objects]
F --> G[Return upload error]
Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Preserve the earliest upload rejection while still awaiting all uploads before cleanup, cover competing failures, and provide redacted configured Worker/R2 boundary evidence before merge. Do we have a high-confidence way to reproduce the issue? Yes for the error-order defect: two deferred upload promises can reject in reverse input order, and the current source will rethrow the lower-index failure. No configured Worker/R2 reproduction was supplied for the cleanup behavior. Is this the best way to solve the issue? No: waiting for all uploads before cleanup is appropriate, but selecting the rejection from the all-settled result array does not preserve prior fail-fast error semantics. 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 justifications:
EvidenceAcceptance criteria:
What 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 (8 earlier review cycles)
|
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.