Skip to content

fix(cloud-agent): link attachment rows before dispatch and require the client message id - #8585

Open
iscekic wants to merge 15 commits into
mainfrom
drain/keep-accepted-sends-and-attachments
Open

iscekic wants to merge 15 commits into
mainfrom
drain/keep-accepted-sends-and-attachments

Conversation

@iscekic

@iscekic iscekic commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Evidence

Evidence: demonstrated — a local end-to-end run on this VM drove the changed path through the real web tRPC endpoint, the real Worker runtime, and real PostgreSQL.

Run at f0cca6b36 on pnpm dev:start cloud-agent --no-attach: Next.js, PostgreSQL, the cloud-agent-next Worker on 8794 (workerd with its Durable Objects), a fake-account user signed in through the real NextAuth fake-login provider, and one Cloud Agent session prepared through the public prepareSession procedure. Attachment rows were admitted directly in the local ledger, so every scenario called only the public sendMessage procedure against the real ledger rows.

Scenario Observed
Send the Worker accepts HTTP 200 with delivery: queued; the row the dispatch linked stayed linked
Send the Worker refuses (a model the session cannot use), with its own freshly linked row HTTP 400 Selected model is not available for this cloud agent session; the router logged Returned the attachments of a refused send { released: 1 } and the row returned to pending
Send the Worker refuses at the balance middleware HTTP 402 Insufficient credits: $1 minimum required; released: 1 and the row returned to pending
Refused send on an attachment path an accepted send already holds HTTP 400, nothing released, and the accepted send's row stayed linked
Send that failed with no Worker answer HTTP 500 fetch failed; nothing released and the row stayed linked

The ledger, personal-router, organization-router, and helper suites pass: 345 tests across four suites, covering the release transition, the reused composer path, the reaper after a release, a 4xx refusal, a 5xx answer, a transport failure, and a failing release. pnpm --filter web type checking passes; oxlint and oxfmt are clean on the changed files.

Limits: the accepted scenario queued the turn without a container start, so no upstream model was involved, and no object store was exercised because the ledger rows were admitted directly. Releasing hands rows back to the abandoned-upload reaper rather than deleting objects from the request path, so object deletion remains the reaper's behaviour, covered by the ledger suite.

Change

  • apps/web/src/lib/r2/cloud-agent-pending-uploads.ts: linkAttachmentsForDispatch now returns the object keys it moved from pending to linked, and releaseUnadmittedUploads moves exactly a supplied key set back to pending.
  • apps/web/src/lib/cloud-agent-next/release-refused-send.ts: after a failed send, the rows are handed back only when the failure is the Worker's own error response with a 4xx status, which the Worker sends before it admits anything. A transport failure, a timeout, or a server-side status keeps the rows linked, and the helper never throws, so the send's own error is what the caller sees.
  • Both sendMessage handlers keep the keys their link returned and pass them to that helper on the failure path. prepareSession is unchanged: a failed prepare does not tell the caller the session id, so a refusal cannot be attributed to a specific turn there.
  • Tests: the release transition and the reused-path case in the ledger suite, the refusal/5xx/transport/no-keys/failing-release answers in the new helper suite, and the router wiring in both router suites.

Retention after a failed send

A send the Worker holds, and any send whose outcome the web cannot attribute, keeps its attachment rows: releasing them could remove an object an accepted message references. A send the Worker answered with a refusal links its own rows and hands exactly those back to the reaper, so an abandoned attachment no longer waits for account deletion. Because the composer reuses one attachment path across sends, the release is bound to the keys this dispatch linked rather than to the path.

Production prerequisites

None.

Proxy answers

The owner's proxy gave these answers in the owner's place.

  1. Question: Ordering: The reaper claims an expired row as 'reaped' and then deletes the object; the post-send link only matches 'pending', so a claim that wins deletes an accepted message's attachment. Which fix do you want? Answer: Link before dispatch (Recommended) Confidence: 0.85
  2. Question: Expired upload: If a draft survives past its 24h lease, the reaper's claim can land before the send links the rows. What should the send do then? Answer: Refuse before dispatch (Recommended) Confidence: 0.80
  3. Question: Failed dispatch: After the pre-dispatch link, the worker call can still fail (possibly after accepting). What happens to the freshly linked rows? Answer: Leave them linked (Recommended) Confidence: 0.60
  4. Question: message id: The send handlers mint a fresh message id when the client omits one, which defeats the worker's replay dedupe for retries. How should the server treat a missing id? Answer: Require it in the schema (Recommended) Confidence: 0.65
  5. Question: Scope: The same post-accept link bug and the same missing-id fallback are in the organization router, and both prepareSession handlers link inside the same try/catch as the dispatch. Which sites should this change cover? Answer: All four sites (Recommended) Confidence: 0.90
  6. Question: Gate recovery: The required merge commit is blocked by persistent judge HTTP 429s. May I temporarily route the local kilo provider to the already configured control-bot credential for this session, then restore it after publication? This affects new local kilo provider lookups; gate code, rules, production configuration, and account provisioning stay unchanged. Answer: Temporary credential routing Confidence: 0.60
  7. Question: Retention: kilobot's one remaining finding is that rows linked before a failed send stay linked with no reclaim except account deletion, and merge-readiness needs its recommendation. A release can be conditional on a signal the worker writes before admission. How should the failed-send case be handled? Answer: Release on confirmed non-admission Confidence: 0.60

The Cloud Agent treats a message id as the durable user-message identity and
replays a repeated id instead of admitting a second turn. The cloud API binding
made the id optional, so a caller could leave it out and lose replay on a
retry. CloudAgentApi.send now requires the id, and the cloud transport refuses
a send without one before the request leaves the client.
…d rows

linkPendingUploads now reports the requested keys whose row is already
'reaped', and the new linkAttachmentsForDispatch helper links one message's
attachment rows before a dispatch and refuses the dispatch when a row was
already released, with an actionable error instead of a turn whose attachment
object is gone.

The abandoned-upload reaper re-checks the lease in the claim statement that
moves a row to 'reaped', so a row refreshed by a retry presign after the batch
was picked is skipped. The claim comments now state the invariant they hold.
…sage id

The personal and organization prepareSession and sendMessage handlers linked a
message's attachment rows after the Worker had answered, inside the same try
whose catch rethrows the Worker error. A ledger write failure therefore
reported an accepted send as failed, and a retry of the reported failure had no
stable id to replay against.

Both handlers now link the rows before the dispatch, so no bookkeeping is left
after the Worker accepts the turn, and they forward the client's message id
instead of minting one. baseSendMessageNextSchema requires messageId, so a
client that omits it is rejected before any Worker call. Comments in the
composer hook describe the dispatch-time link they rely on.
@iscekic iscekic added drain Work item from the findings drain janitor:features-workflows Janitor finding family: features-workflows labels Oct 22, 2026
Comment thread apps/web/src/routers/cloud-agent-next-schemas.ts
Comment thread apps/web/src/lib/r2/cloud-agent-pending-uploads.ts Outdated
Comment thread apps/web/src/routers/cloud-agent-next-router.ts Outdated
Comment thread apps/web/src/lib/r2/cloud-agent-pending-uploads.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental change replaces the single-level status read with a bounded cause-chain walk (max four links) that keeps the existing 4xx-only release gate, handles non-object and cyclic causes, and adds direct coverage of a status carried only on the cause; no new defect was found in the changed lines.

Assumptions: [INFERENCE] node_modules is absent, so whether a real tRPC non-JSON parse failure carries a numeric httpStatus on its cause chain (rather than a status at cause.error.status, the shape isInsufficientCreditsError reads at apps/web/src/lib/cloud-agent-next/cloud-agent-client.ts:541-547) could not be verified. The new test fabricates that cause as a TRPCClientError with data.httpStatus, so if the real parse error keeps no numeric status this path remains a no-op rather than a regression. The rest of the shipping diff was covered by the prior incremental review.

Files Reviewed (2 files)
  • apps/web/src/lib/cloud-agent-next/release-refused-send.ts
  • apps/web/src/lib/cloud-agent-next/release-refused-send.test.ts
Previous Review Summaries (8 snapshots, latest commit a04fa79)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit a04fa79)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The incremental fix routes the credits and billing errors that client.sendMessage synthesizes through the same 4xx gate, so the worker's balance 402 now returns its linked rows; the only remaining pre-admission shape the gate cannot see is a non-JSON 4xx, whose normalized error drops the status.

Assumptions: [INFERENCE] Worker and client internals were read from source, not executed; node_modules is absent, so whether the tRPC client exposes a response status on a non-JSON body could not be checked. The 4xx-before-admission invariant was verified for sendMessageV2 only (balance middleware, requireCurrentSessionAccess, preflightAndQueuePromptMessage/queueMessage), not for the organization or prepare paths. No new memory/object retention was introduced; this change narrows it.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
apps/web/src/lib/cloud-agent-next/release-refused-send.ts 16 normalizeCloudAgentProtocolError (cloud-agent-client.ts:574) replaces a non-JSON response with a plain Error that has no httpStatus, so a 4xx answered by the edge in front of the worker still returns undefined here and its newly linked rows stay linked with no in-repo cleanup.
Files Reviewed (2 incremental files + 2 targeted checks)
  • apps/web/src/lib/cloud-agent-next/release-refused-send.ts - 1 suggestion
  • apps/web/src/lib/cloud-agent-next/release-refused-send.test.ts - no issue (credits 402, billing 402/409, billing 503 and no-status cases assert the intended release)
  • apps/web/src/routers/cloud-agent-next-router.ts - targeted check, no new issue (release stays scoped to the rows this dispatch linked, 4xx statuses come only from pre-admission refusals)
  • apps/web/src/lib/cloud-agent-next/cloud-agent-client.ts - targeted check, no new issue (InsufficientCreditsError = 402, CloudAgentBillingError = 402/409/503, protocol error carries no status)

Previous review (commit 2ce4703)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The new 4xx-based release gate is sound for the errors it recognizes, but client.sendMessage rewrites the worker's insufficient-credits 402 into InsufficientCreditsError, so the most common pre-admission refusal still leaves newly linked attachment rows and their R2 objects linked permanently.

Assumptions: [INFERENCE] Worker internals were read from source, not executed; the 4xx-before-admission invariant was verified for the sendMessageV2 path only (balance middleware, preflight, DO send); TRPCClientError.data.httpStatus availability in the installed tRPC client could not be checked because node_modules is absent, but the repo reads both data and shape.data.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/cloud-agent-next/release-refused-send.ts 9 workerRefusalStatus only recognizes TRPCClientError, but client.sendMessage converts the worker's balance 402 into InsufficientCreditsError (cloud-agent-client.ts:1070), billing failures into CloudAgentBillingError (cloud-agent-client.ts:552), and non-JSON 4xx bodies into CloudAgentProtocolError (cloud-agent-client.ts:574). Those 4xx answers are refused before admission, yet the rows are never released, so they stay linked with no in-repo cleanup.
Files Reviewed (10 files)
  • apps/web/src/lib/cloud-agent-next/release-refused-send.ts - 1 warning
  • apps/web/src/lib/cloud-agent-next/release-refused-send.test.ts - no issue
  • apps/web/src/lib/cloud-agent-next/release-unadmitted-send.ts - deleted, no issue
  • apps/web/src/lib/cloud-agent-next/release-unadmitted-send.test.ts - deleted, no issue
  • apps/web/src/lib/r2/cloud-agent-pending-uploads.ts - no issue
  • apps/web/src/lib/r2/cloud-agent-pending-uploads.test.ts - no issue
  • apps/web/src/routers/cloud-agent-next-router.ts - no issue
  • apps/web/src/routers/cloud-agent-next-router.test.ts - no issue
  • apps/web/src/routers/organizations/organization-cloud-agent-next-router.ts - no issue
  • apps/web/src/routers/organizations/organization-cloud-agent-next-router.test.ts - no issue

Fix these issues in Kilo Cloud

Previous review (commit f4d376e)

Status: 2 Issues Found | Recommendation: Address before merge

Executive Summary

The new post-failure release is keyed by the page-level upload path rather than the dispatch, and its two lookups are not serialized with the Worker's admission queue, so either path can return rows of an accepted message to the reaper.

Assumptions: [INFERENCE] The reaper runs on its normal schedule and claims pending rows only after the stored lease lapses; worker internals were read from source, not executed; only the web composer's upload-path lifecycle was verified (the extension's was not).

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/r2/cloud-agent-pending-uploads.ts 299 releaseUnadmittedUploads filters on message_uuid = attachments.path, but the web composer reuses one attachmentMessageUuid for every send from the page while each send mints a new message id. A retry that reuses the same files and is refused therefore flips the rows an earlier, accepted dispatch linked back to pending, handing its objects to the reaper.
apps/web/src/lib/cloud-agent-next/release-unadmitted-send.ts 56 The two getMessageResult reads are treated as proof of non-admission, but send admits through the DO's enqueue chain while getMessageResult reads this.messages directly. A send the web abandoned can still be queued (or in Worker preflight) when both lookups report not-found, and is then admitted after the rows were released.
Files Reviewed (8 files)
  • apps/web/src/lib/cloud-agent-next/release-unadmitted-send.ts - 1 warning
  • apps/web/src/lib/cloud-agent-next/release-unadmitted-send.test.ts - no issue
  • apps/web/src/lib/r2/cloud-agent-pending-uploads.ts - 1 warning
  • apps/web/src/lib/r2/cloud-agent-pending-uploads.test.ts - no issue
  • apps/web/src/routers/cloud-agent-next-router.ts - no new issue
  • apps/web/src/routers/cloud-agent-next-router.test.ts - no issue
  • apps/web/src/routers/organizations/organization-cloud-agent-next-router.ts - no issue
  • apps/web/src/routers/organizations/organization-cloud-agent-next-router.test.ts - no issue

Fix these issues in Kilo Cloud

Previous review (commit 75a53ca)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

No new defects at HEAD (75a53ca); the merge of main cleanly preserves both the pre-dispatch attachment link and the required client message id, and the one remaining finding is the documented retention of linked rows after a dispatch failure, which main's account-deletion purge now partially bounds.

Assumptions: [INFERENCE] No external R2 lifecycle rule was verified; changes merged in from main were treated as already reviewed, and only the PR's 17 files were re-checked at HEAD.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/routers/cloud-agent-next-router.ts 380 Linked rows stay linked after a failed dispatch; only account deletion reclaims them, so retention for an active account is unbounded

Re-verified at HEAD. linkAttachmentsForDispatch commits the link before client.sendMessage/client.prepareSession runs, and nothing releases the rows when the Worker call fails. Main's merged purgeUserPendingUploads now sweeps ['pending','linked'] during account deletion, which removes the earlier claim that no path matches linked; still, releasePendingUploads and reapAbandonedUploads filter status = 'pending', and linked rows do not count toward the per-account pending bound, so an active account can accumulate linked objects with no time bound. The PR body documents this as an accepted trade-off, since a dispatch failure can arrive after the Worker accepted the turn.

Files Reviewed (17 files)
  • apps/extension/src/shared/extension-agent-session-manager.test.ts - no issue (fixtures carry valid ids)
  • apps/extension/src/shared/extension-trpc-client.test.ts - no issue
  • apps/web/src/hooks/useCloudAgentAttachmentUpload.ts - no issue (comment-only; client does not link pre-send)
  • apps/web/src/lib/r2/cloud-agent-pending-uploads.ts - no new issue (merge keeps main's purge/limit; link + refusal share one transaction)
  • apps/web/src/lib/r2/cloud-agent-pending-uploads.test.ts - no issue
  • apps/web/src/routers/cloud-agent-next-router.ts - 1 carried issue (WARNING above; link precedes dispatch at lines 275 and 380)
  • apps/web/src/routers/cloud-agent-next-router.test.ts - no issue
  • apps/web/src/routers/cloud-agent-next-schemas.ts - no issue (required messageId preserved alongside main's setupCommandsSchema)
  • apps/web/src/routers/cloud-agent-next-schemas.test.ts - no issue (fixture now carries a valid message id)
  • apps/web/src/routers/cloud-agent-next-send-schema.test.ts - no issue
  • apps/web/src/routers/organizations/organization-cloud-agent-next-router.ts - no new issue (same pre-dispatch link; caller-scoped ledger writes)
  • apps/web/src/routers/organizations/organization-cloud-agent-next-router.test.ts - no issue (ordering assertion now requires the Worker call)
  • packages/cloud-agent-sdk/src/cloud-agent-transport.ts - no issue (missing id refused before the request; manager always mints one)
  • packages/cloud-agent-sdk/src/cloud-agent-transport.test.ts - no issue
  • packages/cloud-agent-sdk/src/session-transport.test.ts - no issue
  • packages/cloud-agent-sdk/src/transport.ts - no issue (type requires the id)
  • services/kilo-mcp/catalog.json - no issue (generated sync of the required messageId)

Fix these issues in Kilo Cloud

Previous review (commit 01616de)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

No new defects were found at HEAD (01616de); the single remaining finding is the previously reported unbounded retention of linked attachment rows after any post-link dispatch failure, which the PR documents as an accepted trade-off.

Assumptions: [INFERENCE] No in-repo cleanup path or verified R2 lifecycle rule bounds the objects behind linked rows. The reaper/link interleavings were reasoned from the code and the added tests, not re-executed in this review.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/routers/cloud-agent-next-router.ts 355 Rows linked before dispatch stay linked after any dispatch failure, and no cleanup path matches linked

Re-verified at HEAD. linkAttachmentsForDispatch commits the link before client.sendMessage/client.prepareSession runs, and nothing releases the rows when the Worker call fails. releasePendingUploads, purgeUserPendingUploads and reapAbandonedUploads in apps/web/src/lib/r2/cloud-agent-pending-uploads.ts all filter status = 'pending', and account deletion purges only pending objects before deleting the ledger rows, so objects behind linked rows are stranded. The PR body and the author's replies explicitly accept this trade-off because a dispatch failure can arrive after the Worker accepted the turn; no in-repo age bound or verified external lifecycle rule was provided, so cumulative storage for failed dispatches is unbounded.

Files Reviewed (17 files)
  • apps/extension/src/shared/extension-agent-session-manager.test.ts - no issue (fixtures carry valid ids)
  • apps/extension/src/shared/extension-trpc-client.test.ts - no issue
  • apps/web/src/hooks/useCloudAgentAttachmentUpload.ts - no issue (comment-only change; client does not link pre-send)
  • apps/web/src/lib/r2/cloud-agent-pending-uploads.ts - no new issue (link + refusal share one transaction; ne(status,'linked') read-back covers reaped and failed-delete rows; lease re-check in the claim predicate is sound)
  • apps/web/src/lib/r2/cloud-agent-pending-uploads.test.ts - no issue (mixed-row rollback and reaper interleaving cases are deterministic)
  • apps/web/src/routers/cloud-agent-next-router.ts - 1 carried issue (WARNING above; link precedes dispatch at lines 250 and 355)
  • apps/web/src/routers/cloud-agent-next-router.test.ts - no issue
  • apps/web/src/routers/cloud-agent-next-schemas.ts - no issue (required messageId matches messageIdNextSchema; in-repo callers already mint one)
  • apps/web/src/routers/cloud-agent-next-schemas.test.ts - no issue
  • apps/web/src/routers/cloud-agent-next-send-schema.test.ts - no issue
  • apps/web/src/routers/organizations/organization-cloud-agent-next-router.ts - no new issue (same link-before-dispatch pattern; caller-scoped ledger writes)
  • apps/web/src/routers/organizations/organization-cloud-agent-next-router.test.ts - no issue (ordering assertion now requires the Worker call)
  • packages/cloud-agent-sdk/src/cloud-agent-transport.ts - no issue (missing id refused before the request; manager always mints one)
  • packages/cloud-agent-sdk/src/cloud-agent-transport.test.ts - no issue
  • packages/cloud-agent-sdk/src/session-transport.test.ts - no issue
  • packages/cloud-agent-sdk/src/transport.ts - no issue (type now requires the id)
  • services/kilo-mcp/catalog.json - no issue (generated sync of the required messageId)

Fix these issues in Kilo Cloud

Previous review

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The incremental commit adds the required messageId to the three remaining send-schema cap fixtures, so those tests still pass on the field they intend to exercise; the only remaining finding is the previously reported unbounded retention of linked attachment rows after any post-link dispatch failure.

Assumptions: [INFERENCE] No in-repo bound exists for the R2 objects behind linked rows, and whether an out-of-repo lifecycle rule covers them could not be verified.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/routers/cloud-agent-next-router.ts 355 Rows linked before dispatch stay linked after any dispatch failure, and no cleanup path matches linked

Re-verified at HEAD. linkAttachmentsForDispatch commits the link before client.sendMessage runs, and nothing releases the rows when the Worker call fails. releasePendingUploads, purgeUserPendingUploads and reapAbandonedUploads in apps/web/src/lib/r2/cloud-agent-pending-uploads.ts all filter status = 'pending', and account deletion purges only pending objects before deleting every ledger row, so objects behind linked rows are stranded with no remaining handle. The PR body documents this as an accepted trade-off rather than a fix, and no in-repo age bound was added.

Files Reviewed (3 files)
  • apps/web/src/routers/cloud-agent-next-send-schema.test.ts - no new issue (added messageId matches messageIdNextSchema; cap assertions still fail/succeed for the intended reason)
  • apps/web/src/routers/cloud-agent-next-router.ts - carried WARNING above (targeted re-check of pre-dispatch link ordering)
  • apps/web/src/lib/r2/cloud-agent-pending-uploads.ts - carried WARNING above (targeted re-check of cleanup filters)

Previous review (commit 55fa9cf)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The incremental commit fixes both previously reported findings by keeping the link transaction legitimate and updating the send-schema fixtures; the remaining issue is the unbounded retention of linked rows and their objects when a dispatch fails after the pre-dispatch link.

Assumptions: [INFERENCE] No R2 lifecycle rule for the cloud-agent attachments bucket exists in this repository, so objects behind linked rows have no in-repo expiry. Whether an out-of-repo lifecycle rule exists could not be verified.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/routers/cloud-agent-next-router.ts 355 Rows linked before dispatch stay linked after any dispatch failure, and no cleanup path matches linked

Re-verified at HEAD and still present, unchanged by this commit. linkAttachmentsForDispatch commits the link before client.sendMessage runs, and nothing releases the rows when the Worker call fails. releasePendingUploads (apps/web/src/lib/r2/cloud-agent-pending-uploads.ts:326-332), purgeUserPendingUploads (:340-345) and reapAbandonedUploads (:368-376) all filter status = 'pending', and account deletion (apps/web/src/lib/user/index.ts:1523-1526) purges only pending objects before deleting every ledger row, so the objects behind linked rows are stranded with no remaining handle. This commit widens the set of such rows because a failed dispatch now leaves them linked instead of pending. The PR body accepts the retention trade-off, but with no in-repo bound the retention is not actually bounded, and the account-deletion path is the worst case.

Files Reviewed (5 files)
  • apps/web/src/lib/r2/cloud-agent-pending-uploads.ts - no new issue (transaction rollback verified against the pg-backed drizzle client; ne(status, 'linked') read-back is scoped by user, message and key, and object_key is unique)
  • apps/web/src/lib/r2/cloud-agent-pending-uploads.test.ts - no new issue (rollback test is deterministic; no R2 call or timing dependency)
  • apps/web/src/routers/cloud-agent-next-schemas.test.ts - no new issue (messageId fixture matches the schema format)
  • apps/web/src/routers/cloud-agent-next-send-schema.test.ts - no new issue (previously reported fixture gap is now closed on all positive and negative cases)
  • apps/web/src/routers/organizations/organization-cloud-agent-next-router.test.ts - no new issue (added toHaveBeenCalledTimes(1) makes the ordering assertion meaningful)

Targeted re-checks outside the incremental diff: cloud-agent-next-router.ts (carried WARNING above) and organization-cloud-agent-next-router.ts (prepareSession/sendMessage share the same pre-dispatch link ordering; same retained-row behavior, no separate new finding).

Previous review (commit 22de599)

Status: 2 Issues Found | Recommendation: Address before merge

Executive Summary

The incremental transaction refactor fixes the reaped-row race and the partial-link side effect, but the required messageId still breaks an untouched schema suite and rows linked by a failed dispatch remain unreachable by every cleanup path.

Assumptions: [INFERENCE] Whether an out-of-repo R2 lifecycle rule bounds objects behind linked rows could not be verified; no in-repo cleanup for linked rows exists. The retention behavior is reasoned from the code, not reproduced.

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/routers/cloud-agent-next-schemas.ts 598 Required messageId still fails cloud-agent-next-send-schema.test.ts, which the last commit did not update
apps/web/src/routers/cloud-agent-next-router.ts 355 Rows linked before dispatch stay linked after any dispatch failure; reaper, release and account-deletion purge all match only pending

apps/web/src/routers/cloud-agent-next-send-schema.test.ts (matched by jest.config.ts testMatch: **/src/**/*.test.ts, not ignored) calls baseSendMessageNextSchema.safeParse without messageId and asserts success === true at lines 39, 48, 72, 80 and 89. Only cloud-agent-next-schemas.test.ts received the fixture update in commit 7e4a4d4, so pnpm test in apps/web is still red.

For the retained rows, the failure is not bounded in-repo: releasePendingUploads (cloud-agent-pending-uploads.ts:326-332), purgeUserPendingUploads (:340-345) and reapAbandonedUploads (:368-376) all filter status = 'pending', and account deletion (apps/web/src/lib/user/index.ts:1523) purges only pending objects before deleting every ledger row for the user. A dispatch that fails after the link therefore retains the ledger rows and their objects with no handle left after account deletion. The PR body accepts this trade-off, but the composer release path cited there cannot release them because it also matches only pending rows.

Files Reviewed (6 files)
  • apps/web/src/lib/r2/cloud-agent-pending-uploads.ts - no new issue (transaction now covers the reaped read-back and the refusal)
  • apps/web/src/lib/r2/cloud-agent-pending-uploads.test.ts - no new issue
  • apps/web/src/routers/cloud-agent-next-schemas.test.ts - no new issue
  • apps/web/src/routers/organizations/organization-cloud-agent-next-router.test.ts - no new issue (ordering assertion now requires one Worker call)
  • apps/web/src/routers/cloud-agent-next-schemas.ts - 1 carried issue (same active comment)
  • apps/web/src/routers/cloud-agent-next-router.ts - 1 carried issue (still present at HEAD)

Reviewed by deepseek-flash-latest · Input: 58.4K · Output: 15.1K · Cached: 601.5K

Review guidance: REVIEW.md from base branch main

… fixture

linkAttachmentsForDispatch now links and refuses in one transaction, so a
message that cannot be dispatched leaves every row as it was instead of pinning
the rows that could be linked. The link reports every requested key the message
does not hold, which also covers a row a failed object delete returned to
pending and therefore made reapable again.

The schema test's send fixture carries the now-required message id, and the
organization ordering assertion also requires the Worker call to have happened.
The dispatch schema now requires the client message id, so the send fixtures in
cloud-agent-next-send-schema.test.ts carry one.
@iscekic

iscekic commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

bot: On the remaining warning, retention of linked rows after a failed dispatch is the intended trade-off, and it is stated in the pull request body.

A dispatch failure can arrive after the Worker accepted the turn, so releasing the rows would delete an object an accepted message references, which is the failure this change removes. The rows stay linked and their objects stay in the bucket, bounded per message by the attachment count.

The account-deletion detail is real but pre-existing and independent of this change: purgeUserPendingUploads only purges pending objects before deleting the ledger rows, so the objects of every delivered message's linked rows already lose their only handle at account deletion today. Fixing that needs its own change to the deletion path; it is not part of restoring an accepted send's attachments.

@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. labels Oct 22, 2026
@iscekic iscekic added the merge:blocked:insufficient-e2e Verification does not cover the change label Oct 22, 2026
…ends-and-attachments

# Conflicts:
#	apps/web/src/routers/cloud-agent-next-send-schema.test.ts
@iscekic iscekic removed merge:human Substantive functional risk requires human review. merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:blocked:insufficient-e2e Verification does not cover the change labels Oct 23, 2026
@iscekic

iscekic commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Merged main and preserved both the required client message id and the shared payload-length boundary. Added message ids to the three incoming boundary fixtures. The affected ledger, schema, personal-router, and organization-router suites pass: 537 tests across five suites. Web type checking also passes. The head review repeats the already accepted retention trade-off: linked objects are retained after ambiguous dispatch failure, with no verified age bound. Automatic release would violate the approved accepted-send safety contract; that warning remains documented rather than being described as fixed.

@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:blocked:insufficient-e2e Verification does not cover the change merge:human Substantive functional risk requires human review. merge:blocked:judge-unavailable Risk assessment unavailable merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. and removed merge:blocked:insufficient-e2e Verification does not cover the change merge:human Substantive functional risk requires human review. labels Oct 23, 2026
@iscekic iscekic added merge:human Substantive functional risk requires human review. and removed merge:blocked:insufficient-e2e Verification does not cover the change labels Oct 24, 2026
…admit it

After a send fails, the router asks the worker whether that message exists.
Only a message the worker reports as not found hands the attachment rows back
to the abandoned-upload reaper; a message the worker holds, and an unavailable
lookup, keep them linked.
@iscekic iscekic removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. labels Oct 24, 2026
@iscekic

iscekic commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

bot: At f4d376e42 a failed send now asks the worker whether that message exists and hands its attachment rows back to the abandoned-upload reaper only when the worker reports no such message; a message the worker holds, and an unavailable lookup, keep the rows linked.

On the local stack: a send the worker accepted queued with the row linked; a send the worker refused at model preflight returned the row to pending and logged Returned the attachments of an unadmitted send { released: 1 }; a send with the worker unreachable kept the row linked and logged Could not confirm a failed send was not admitted. The helper suite, ledger suite, and both router suites pass (340 tests), and web type checking, oxlint, and oxfmt are clean.

Comment thread apps/web/src/lib/r2/cloud-agent-pending-uploads.ts Outdated
Comment thread apps/web/src/lib/cloud-agent-next/release-unadmitted-send.ts Outdated
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. merge:blocked:insufficient-e2e Verification does not cover the change labels Oct 24, 2026
The link now reports the rows it moved to linked, and the failure path hands
back exactly those keys and only when the worker itself refused the turn. A
composer path reused by a later send therefore cannot free an accepted
message's attachments, and a transport or server-side failure keeps them.
@iscekic iscekic removed merge:human Substantive functional risk requires human review. merge:blocked:insufficient-e2e Verification does not cover the change merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head labels Oct 24, 2026
Comment thread apps/web/src/lib/cloud-agent-next/release-refused-send.ts Outdated
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. labels Oct 24, 2026
The send path rewrites some worker answers into its own error classes, so the
release now reads the status from any error that carries one and keeps the
rows for a transport failure and a server-side status.
@iscekic iscekic removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. labels Oct 24, 2026
Comment thread apps/web/src/lib/cloud-agent-next/release-refused-send.ts Outdated
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. labels Oct 24, 2026
A non-JSON answer is wrapped with the original error as its cause, so the
release reads the status along that chain too and still keeps the rows when no
status is present.
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. and removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. labels Oct 24, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drain Work item from the findings drain janitor:features-workflows Janitor finding family: features-workflows merge:human Substantive functional risk requires human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants