Skip to content

fix(packages): reclaim unadopted multipart uploads - #3736

Merged
Patrick-Erichsen merged 2 commits into
mainfrom
pe/inbox-package-multipart-3673
Sep 16, 2026
Merged

Patrick-Erichsen merged 2 commits into
mainfrom
pe/inbox-package-multipart-3673

Conversation

@Patrick-Erichsen

@Patrick-Erichsen Patrick-Erichsen commented Sep 16, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #3673.

Multipart package publication stored tarballs and extracted/loose files before validation, leaving them orphaned when publication rejected. Track only request-created blobs, settle every in-flight store before parse cleanup, and transfer cleanup responsibility to the internal publication action. The action reclaims files until a newly committed release adopts them; reused upload-ticket artifacts and committed release files survive retries and later failures.

Thanks to @SebTardif for reporting this and the earlier, closed-unmerged implementation in #3613. This replacement addresses its two deletion-safety findings: reused staged tarballs and errors after publication commits.

Real before/after proof: disposable local Convex 1.44.0, authenticated HTTP multipart requests through the real publication actions and storage. Baseline be87bfd9d3f8; candidate is this PR. Counts below are new blobs left without a release after the same operation.

Operation Before After
Account-age rejection, loose files 4 0
Account-age rejection, tarball plus extracted files 5 0
Duplicate publication 5 0
Pending-version retry rejection 5 0
Removed organization publisher reuses an upload ticket 4 0
Partial loose-file storage failure 3 0
Partial tarball extraction storage failure 4 0

Successful published and pending releases retained all five expected blobs. The removed-member replay preserved the original release files and reused tarball: all five still downloaded with HTTP 200. Deliberately injected failures after real published-release insertion and pending-release/attempt insertion returned HTTP 400 while all five committed blobs remained readable, before and after. Partial-store failures used a temporary local wrapper that rejects one store while sibling writes still use actual Convex storage. All temporary routes, helpers, and fault injections were removed, and the clean function set was pushed with deploy typechecking before shutdown.

An ambiguous action-RPC failure does not authorize HTTP deletion. Interrupted actions, dispatches that never arrive, or failed best-effort deletions can still leave unreferenced blobs; this change adds no durable orphan collector.

Validation: focused HTTP/package tests (859 passed); bun run ci:static; bun run ci:unit --maxWorkers=4 (6,968 passed, 3 skipped); bun run ci:packages; bun run ci:types-build; standalone schema, CLI, and Convex tsc --noEmit; clean local Convex push with typechecking. The default-concurrency full suite twice timed out on an unchanged 80 MiB archive fixture; that fixture passed alone in 1.60 seconds, and the complete coverage gate passed with four workers without changing tests, timeouts, or coverage thresholds. Structured autoreview: no findings.

Authenticated CI runtime

The disposable local-auth backend now defaults to two active V8 isolates, preserving explicit overrides (including 0 for unlimited). This uses Convex's execution scheduler to reduce CPU contention. The one-second UDF watchdog and existing system/admission deadlines, browser assertions and retries are unchanged.

Comparable native Linux ARM64 proof (no x86 emulation), using the same application/backend revisions and fresh state:

Runtime Observed result
1.5 CPU quota, unlimited Reproduced matching one-second publisher/user/publish-attempt failures in backend logs and real browser errors; stopped after reproduction (Playwright exit 130, interrupted).
Same 1.5 CPU quota, cap 2 Full unchanged moderation test passed (1.7m), with no watchdog failures; one HTTP version-read admission rejection remained under this severe quota.
Normal 3 CPU quota Unlimited baseline passed (56.1s); cap 2 passed (1.0m) with no watchdog, slow-function, admission, or system-timeout errors.
CPU-bound query, cap 2 Still rejected by the unchanged one-second watchdog.

This establishes a mitigation under controlled CPU contention, not Blacksmith's specific root cause or an optimal cap. The same hosted timeout symptom also occurred on main without this package patch. Original package storage proof and product code remain unchanged.

Validation: 33 focused runner tests; bun run ci:static; final docs format check; bun run ci:unit --maxWorkers=4; explicit schema, CLI and Convex TypeScript checks. Independent review found no actionable issues. The final source with no caller override passed the full native test at 3 CPU quota (1.3m), with no watchdog, admission, or system-timeout errors. Full unit coverage passed: 6,969 tests passed / 3 skipped.

@Patrick-Erichsen
Patrick-Erichsen requested a review from a team as a code owner September 16, 2026 05:23
@clawsweeper

clawsweeper Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@vercel

vercel Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
clawhub Ready Ready Preview Sep 16, 2026 6:04am UTC

Request Review

@blacksmith-sh

This comment has been minimized.

@clawsweeper

clawsweeper Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed September 16, 2026, 2:03 AM ET / 06:03 UTC (Revision 2).

ClawSweeper review

What this changes

Reclaims newly uploaded package files that no release adopts and limits disposable authenticated-test backends to two active isolates while preserving explicit overrides.

Merge readiness

✅ Ready for maintainer review

No blocking findings. Current main still lacks multipart upload reclamation, and the supplied real-Convex evidence supports the cleanup boundaries. This collaborator-authored PR remains a useful implementation candidate.

Priority: P2
Reviewed head: e17c766ce74a488cdae73dbf977f99d356328b16

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) A focused, well-supported lifecycle repair with relevant regression coverage and real runtime observations; no actionable defect was found.
Proof confidence 🐚 platinum hermit (4/6) Sufficient (live_output): The captured body reports real authenticated multipart requests through the changed HTTP/action/storage path on Convex 1.44.0, demonstrating reclamation, removed-member rejection, staged-artifact preservation, and retention after commit failures. Native runtime comparisons exercise the new isolate default without weakening the watchdog.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (live_output): The captured body reports real authenticated multipart requests through the changed HTTP/action/storage path on Convex 1.44.0, demonstrating reclamation, removed-member rejection, staged-artifact preservation, and retention after commit failures. Native runtime comparisons exercise the new isolate default without weakening the watchdog.
Evidence reviewed 9 items Current main still needs the fix: The fetched main handler stores multipart contents before dispatching publication, then maps failures to responses without reclaiming request-created blobs. Existing legacy-ZIP cleanup covers a separate allocation.
Request cleanup authority: Only storage IDs returned by this request's stores enter the cleanup set. All sibling stores settle before parsing cleanup; reused upload-ticket tarballs remain outside the set, and HTTP relinquishes cleanup before action dispatch.
Release adoption and authorization: The action marks files adopted immediately after a new release insertion returns, before subsequent fallible work. Reused releases do not adopt candidate files. The internal-only cleanup argument is absent from the public publication API; release insertion retains token, publisher, and membership checks.
Findings None None.
Security None None.

How this fits together

ClawHub package publication accepts authenticated multipart uploads, stores their contents in Convex, and attaches them to published or pending releases. Cleanup must distinguish newly uploaded files from reusable staged artifacts and files already owned by releases.

flowchart TD
  A[Authenticated package upload] --> B[Store and track new blobs]
  B --> C{Parsing succeeds?}
  C -->|No| D[Reclaim request blobs]
  C -->|Yes| E[Validate and publish]
  E --> F{New release adopts files?}
  F -->|No| D
  F -->|Yes| G[Retain release files]
  H[Reusable staged tarball] --> E
Loading

Before merge

None.

Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Diff size +303/-47 across 8 files The branch contains package cleanup plus a bounded local-auth runtime mitigation.
Production and test growth Production +72 net; tests +165 net; specs +17 net Production growth implements request ownership tracking and the documented isolate default, with focused failure-path coverage.

Root-cause cluster

Relationship: fixed_by_candidate
Canonical: #3673
Summary: This PR explicitly addresses the tracked multipart storage leak and replaces the acknowledged closed-unmerged attempt.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Technical review

Best possible solution:

Keep cleanup tied to request provenance and release adoption; durable collection of interrupted uploads can remain separate work.

Do we have a high-confidence way to reproduce the issue?

Yes: current-main source stores multipart blobs before publication validation and lacks request cleanup on rejection; the contributor additionally supplies real before/after Convex observations. This read-only review did not execute a reproduction.

Is this the best way to solve the issue?

Yes for package cleanup: the action owns the release-adoption decision, making blanket HTTP deletion unsafe. The isolate cap is an acceptable measured mitigation; raising watchdog limits would weaken the existing test boundary.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against b0fee70b989c.

Labels

Label changes:

  • add P2: Failed authenticated package publishes leak storage resources, with a bounded cleanup repair and no demonstrated urgent outage.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The captured body reports real authenticated multipart requests through the changed HTTP/action/storage path on Convex 1.44.0, demonstrating reclamation, removed-member rejection, staged-artifact preservation, and retention after commit failures. Native runtime comparisons exercise the new isolate default without weakening the watchdog.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.

Label justifications:

  • P2: Failed authenticated package publishes leak storage resources, with a bounded cleanup repair and no demonstrated urgent outage.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • proof: sufficient: Contributor real behavior proof is sufficient. The captured body reports real authenticated multipart requests through the changed HTTP/action/storage path on Convex 1.44.0, demonstrating reclamation, removed-member rejection, staged-artifact preservation, and retention after commit failures. Native runtime comparisons exercise the new isolate default without weakening the watchdog.

Evidence

What I checked:

  • Current main still needs the fix: The fetched main handler stores multipart contents before dispatching publication, then maps failures to responses without reclaiming request-created blobs. Existing legacy-ZIP cleanup covers a separate allocation. (convex/httpApiV1/packagesV1.ts:2666, b0fee70b989c)
  • Request cleanup authority: Only storage IDs returned by this request's stores enter the cleanup set. All sibling stores settle before parsing cleanup; reused upload-ticket tarballs remain outside the set, and HTTP relinquishes cleanup before action dispatch. (convex/httpApiV1/packagesV1.ts:1393, e17c766ce74a)
  • Release adoption and authorization: The action marks files adopted immediately after a new release insertion returns, before subsequent fallible work. Reused releases do not adopt candidate files. The internal-only cleanup argument is absent from the public publication API; release insertion retains token, publisher, and membership checks. (convex/packages.ts:9343, e17c766ce74a)
  • Captured real behavior evidence: The supplied body at sourceRevision b411e0e9856450a01c61330ab14b0f9b7a3f0e4224048552042a1bc8e518962a reports authenticated HTTP requests through disposable Convex 1.44.0 actions and real storage: seven rejection/retry/fault scenarios leave zero new orphan blobs after the fix. Removed-member replay preserves the original five downloadable blobs; published and pending releases retain their files after injected post-insertion failures. Native ARM64 comparisons also exercise the isolate cap under constrained and normal CPU quotas, preserving watchdog rejection of a CPU-bound query. (e17c766ce74a)
  • Review continuity and introduced scope: Compared with the previously reviewed head, only the local-auth configuration, its tests, and CI spec changed; package cleanup code is unchanged. The previous completed review supplied no findings or published rank-up moves. All eight introduced paths were inspected, independently of endpoint drift. (scripts/playwright-local-auth-config.ts:24, e17c766ce74a)
  • Dependency contract signal: The runner passes FUNRUN_ISOLATE_ACTIVE_THREADS into the disposable Convex backend environment. This explicit execution dependency makes Convex's scheduler and timeout contract relevant; explicit values, including zero, are preserved. (scripts/run-playwright-local-auth.ts:562, e17c766ce74a)

Likely related people:

  • unknown: The claimed source-line change could not be verified from bounded local history. (role: source history unknown; confidence: low)
  • Peter Steinberger: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (1 earlier review cycle)
  • reviewed 2026-09-16T05:27:30.224Z sha 5b74cb6 :: needs maintainer review before merge. :: none

@Patrick-Erichsen
Patrick-Erichsen merged commit 65639a9 into main Sep 16, 2026
36 of 38 checks passed
@Patrick-Erichsen
Patrick-Erichsen deleted the pe/inbox-package-multipart-3673 branch September 16, 2026 06:05

This branch was successfully deployed

1 active deployment
Preview – clawhub — e17c766c Deployed Sep 16, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multipart package publish leaves Convex blobs after a failed publish

1 participant