| ⦠| ⦠| @@ -0,0 +1,50 @@ | |
| 39 | + | message: "Uploaded attachments need a thread id at launch.", | |
| 40 | + | }); | |
| 41 | + | } | |
| 42 | + | const claimed = yield* AttachmentClaims.claimPendingAttachments({ | |
š High orchestration-v2/ThreadMessageIntake.ts:42
Retrying an already accepted launch after a lost response copies the pending attachment to a new random thread-scoped path, then launches.launch returns the existing receipt without referencing that copy. The unreferenced file is never released, so each retry permanently leaks an attachment; perform the idempotency check before claiming or release claimed.claimedPaths when the launch is already committed.
š Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/server/src/orchestration-v2/ThreadMessageIntake.ts around line 42: |
| 2 | |
| 3 | Retrying an already accepted launch after a lost response copies the pending attachment to a new random thread-scoped path, then `launches.launch` returns the existing receipt without referencing that copy. The unreferenced file is never released, so each retry permanently leaks an attachment; perform the idempotency check before claiming or release `claimed.claimedPaths` when the launch is already committed. |
Fixed in c80aae28752480aca3d0ec0d9a773dca4586e821. Shared intake now removes claims not referenced by the returned durable message/events, including accepted launch, command, and send replays. It also removes copies on typed receipt/planning rejections and launch project/receipt failures before message dispatch. The real V2 regression now proves replays leave only accepted files and missing-project/thread requests leave no orphaned copies. The same regression retains the lost-result-after-acceptance case and verifies the original bytes survive. Generic launch/dispatch errors remain ambiguous, so blanket tapError cleanup would reintroduce data loss; those copies are intentionally retained. No new receipt store, journal, lock, or read-after-failure query was added. Focused suites: 38 passed; server typecheck and targeted lint/format passed.
| ⦠| ⦠| @@ -0,0 +1,50 @@ | |
| 46 | + | return yield* launches.launch({ | |
| 47 | + | ...input, | |
| 48 | + | initialMessage: { ...input.initialMessage, attachments: claimed.attachments }, | |
| 49 | + | }); | |
š High orchestration-v2/ThreadMessageIntake.ts:46
launchThread leaks the copied attachment whenever launches.launch fails before accepting the initial message. claimPendingAttachments creates a non-pending final path, so the pending sweep never reclaims it and repeated failed launches accumulate orphaned files; release claimed.claimedPaths on launch failure.
| 1 | - return yield* launches.launch({ |
| 2 | + return yield* launches.launch({ |
| 3 | ...input, |
| 4 | initialMessage: { ...input.initialMessage, attachments: claimed.attachments }, |
| 5 | - }); |
| 6 | + }).pipe( |
| 7 | + Effect.tapError(() => |
| 8 | + AttachmentClaims.releaseClaimedAttachments(claimed.claimedPaths), |
| 9 | + ), |
| 10 | + ); |
Also found in 2 other location(s)
apps/server/src/ws.ts:1319
ThreadMessageIntake.dispatchCommandclaims and copies pending attachments before dispatch, but the new path no longer releasesclaimedPathsifthreads.dispatchfails before accepting the command. For example, dispatching a valid staged upload to a deleted/nonexistent thread copies it under that thread id, thenensureCommandTranscripts/the orchestrator can fail.sweepStalePendingAttachmentsonly deletes ids in the pending segment, so this final-path copy is retained indefinitely despite no message referencing it; repeated failed submissions accumulate orphaned attachment files.
apps/server/src/ws.ts:1437
ThreadMessageIntake.launchThreadcopies pending attachments beforelaunches.launch, but no longer removes the copy when launch fails before the initial message is committed. A launch using a staged attachment and a nonexistent project fails duringThreadLaunchService.launch's project lookup, leaving the copied final attachment unreferenced. These ids are not in the pending segment, sosweepStalePendingAttachmentswill never reclaim them; failed launch attempts leak attachment files.
š Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/server/src/orchestration-v2/ThreadMessageIntake.ts around lines 46-49: |
| 2 | |
| 3 | `launchThread` leaks the copied attachment whenever `launches.launch` fails before accepting the initial message. `claimPendingAttachments` creates a non-pending final path, so the pending sweep never reclaims it and repeated failed launches accumulate orphaned files; release `claimed.claimedPaths` on launch failure. |
| 4 | |
| 5 | Also found in 2 other location(s): |
| 6 | - apps/server/src/ws.ts:1319 -- `ThreadMessageIntake.dispatchCommand` claims and copies pending attachments before dispatch, but the new path no longer releases `claimedPaths` if `threads.dispatch` fails before accepting the command. For example, dispatching a valid staged upload to a deleted/nonexistent thread copies it under that thread id, then `ensureCommandTranscripts`/the orchestrator can fail. `sweepStalePendingAttachments` only deletes ids in the pending segment, so this final-path copy is retained indefinitely despite no message referencing it; repeated failed submissions accumulate orphaned attachment files. |
| 7 | - apps/server/src/ws.ts:1437 -- `ThreadMessageIntake.launchThread` copies pending attachments before `launches.launch`, but no longer removes the copy when launch fails before the initial message is committed. A launch using a staged attachment and a nonexistent project fails during `ThreadLaunchService.launch`'s project lookup, leaving the copied final attachment unreferenced. These ids are not in the pending segment, so `sweepStalePendingAttachments` will never reclaim them; failed launch attempts leak attachment files. |
Fixed in c80aae28752480aca3d0ec0d9a773dca4586e821. Shared intake now removes claims not referenced by the returned durable message/events, including accepted launch, command, and send replays. It also removes copies on typed receipt/planning rejections and launch project/receipt failures before message dispatch. The real V2 regression now proves replays leave only accepted files and missing-project/thread requests leave no orphaned copies. The same regression retains the lost-result-after-acceptance case and verifies the original bytes survive. Generic launch/dispatch errors remain ambiguous, so blanket tapError cleanup would reintroduce data loss; those copies are intentionally retained. No new receipt store, journal, lock, or read-after-failure query was added. Focused suites: 38 passed; server typecheck and targeted lint/format passed.