Skip to content

fix(uploadstore): preserve default transport while bounding R2 headers - #183

Merged
steipete merged 2 commits into
openclaw:mainfrom
SebTardif:fix/f002-r2-fallback-timeout
Aug 30, 2026
Merged

fix(uploadstore): preserve default transport while bounding R2 headers#183
steipete merged 2 commits into
openclaw:mainfrom
SebTardif:fix/f002-r2-fallback-timeout

Conversation

@SebTardif

@SebTardif SebTardif commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

R2 header waits are now bounded without bypassing a process's configured http.DefaultTransport. The default client uses one delegating RoundTripper, replacing the concrete-transport clone/bare-client split and the submitted replacement transport. An explicitly supplied R2Config.HTTPClient remains the complete, unchanged override; production openUploadStorage uses the default client.

The 30-second header timer starts only after a successful complete request write, through composed httptrace.WroteRequest. It stops when complete headers return, not at the first byte. Retry GetConn disarms the previous wait. A mutex, phase, and generation prevent late timer callbacks or hooks from cancelling a later phase. One child context and response wrapper release resources on error, EOF, or Close without cancelling a successful body at RoundTrip return. Client.Timeout remains zero.

Exact after-write timing relies on Go's trace/cancellation lifecycle and wrappers forwarding it. An opaque transport that ignores those hooks or cancellation cannot offer the same bound. Body-read idle limits remain in #182.

Proof

  • Fail-before on main: all three R2 methods remained blocked beyond the 30-second header bound with a configured wrapper. A real origin sent an incomplete header and remained blocked until the 40-second cleanup deadline; a progressing 35-second PUT succeeded.
  • Pass-after: real HTTP tests use the configured wrapper, cancel incomplete headers at the default 30-second bound, and complete the same progressing PUT beyond 30 seconds.
  • Deterministic lifecycle tests cover composed hooks, retry disarming, failed writes, concurrent/late callbacks, an already-expired callback crossing the header/body boundary, parent cancellation, EOF/Close, Go's nil-response/body contracts, and custom-client redirect policy.
  • Full go test ./..., go vet ./..., and focused go test -race passed. Isolated P0-scope review found no accepted actionable finding.

The prior ClawSweeper finding and rank-up move are addressed: the configured wrapper stays in the request path and its invocation is proven. Synthetic local origins and credentials only; no production bucket was accessed. Thanks @SebTardif for the original report/fix; contributor history and credit are preserved.

Exact head 2522032521d5e6d3482636b9f5dff5ee15dcdc5f: Go, TypeScript, full Playwright, Docker and all three desktop platforms passed. The immutable test binary's real HTTP receipt measured incomplete-header cancellation at30.002s and successful progressingPUT at35.082s. Fresh ClawSweeper review found no new correctness defect and marked the PR ready for maintainer review.

The remaining review caveat is the accepted, documented transport contract: an opaque wrapper that omits trace hooks or ignores cancellation cannot provide equivalent after-write timing. A fixed whole-request timer would break progressing PUTs, so no such fallback is added.

When http.DefaultTransport is not *http.Transport, defaultR2HTTPClient
returned &http.Client{} with no ResponseHeaderTimeout. A stalled R2
origin then hung Save, Delete, and Serve until the caller disconnected.

Always attach a Transport with ResponseHeaderTimeout. Leave Client.Timeout
at 0 so streaming stays intact.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@SebTardif
SebTardif requested a review from a team as a code owner August 30, 2026 00:20
@clawsweeper

clawsweeper Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

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

@clawsweeper clawsweeper Bot added merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Aug 30, 2026
@clawsweeper

clawsweeper Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed August 30, 2026, 6:54 PM ET / 22:54 UTC.

ClawSweeper review

What this changes

The PR routes default R2 save, delete, and download requests through the configured default HTTP transport while adding a 30-second post-write response-header deadline and preserving explicit client overrides.

Regression provenance

Possible regression — probable (reviewed change; failure trace). No predecessor PR is attributed.

Merge readiness

⚠️ Ready for maintainer review - 2 items remain

Keep this PR open for normal merge review: it fixes the current main fallback that returns a bare client for wrapped default transports, and the revised transport addresses the prior wrapper-bypass finding without a new correctness defect found.

Priority: P2
Reviewed head: 2522032521d5e6d3482636b9f5dff5ee15dcdc5f

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) The patch is focused, resolves the prior blocker, and has substantial lifecycle coverage plus stated real HTTP after-fix evidence.
Proof confidence 🐚 platinum hermit (4/6) Sufficient (terminal): The changed production owner is the default R2 client used by server upload storage; the PR supplies a real local HTTP-origin scenario through that path, reporting that incomplete headers time out after the fix while a 35-second progressing PUT completes through the configured wrapper.
Patch quality 🦞 diamond lobster (5/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (terminal): The changed production owner is the default R2 client used by server upload storage; the PR supplies a real local HTTP-origin scenario through that path, reporting that incomplete headers time out after the fix while a 35-second progressing PUT completes through the configured wrapper.
Evidence reviewed 6 items Current-main defect: The base implementation returns a bare HTTP client when the configured default transport is a wrapper rather than *http.Transport, leaving that R2 request without a response-header timeout.
Introduced repair: The PR delegates to the configured default transport, composes HTTP trace hooks, starts the timer only after a successful write, and cancels it before body handling.
Production caller boundary: The server's upload-storage construction supplies no custom HTTP client, so R2 uses this default-client behavior; callers that supply R2Config.HTTPClient continue to own transport policy.
Findings None None.
Security None None.

How this fits together

R2 upload storage sends object save, delete, and download requests through an HTTP client. The new transport sits between those operations and the process-wide HTTP transport, enforcing a bounded response-header wait before response bodies reach the upload handlers.

flowchart LR
A[R2 save delete download] --> B[Default R2 HTTP client]
B --> C[Delegating R2 transport]
C --> D[Configured default HTTP transport]
D --> E[R2 origin]
E --> F[Headers or stalled response]
C --> G[Header deadline]
F --> G
G --> H[Response body or timeout]
Loading

Before merge

  • Resolve merge risk (P1) - R2 requests now intentionally honor process-wide default-transport wrappers; wrappers that do not propagate HTTP trace hooks or cancellation cannot receive the documented 30-second header bound.
  • Complete next step (P2) - The prior actionable finding is addressed and no discrete repair remains; this PR needs ordinary maintainer merge review.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Production versus test delta production +117, tests +312, docs/release notes +9, 14 removed The transport lifecycle change is accompanied by focused real-HTTP and deterministic lifecycle coverage.

Merge-risk options

Maintainer options:

  1. Accept the documented transport contract (recommended)
    Land the change with the explicit-client override and documentation that opaque default-transport wrappers cannot provide the header deadline.

Technical review

Best possible solution:

Merge the delegating transport with its documented explicit-client escape hatch so wrapped default transports remain effective while hook-aware transports receive bounded header waits.

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

Yes, at source level: current main returns a bare client for a wrapped default transport, and the supplied real-origin test scenario directly exercises the resulting unbounded header wait.

Is this the best way to solve the issue?

Yes. Delegating through the configured transport, timing only the post-write header phase, and retaining an unchanged explicit-client override is the narrowest solution to the verified fallback defect.

AGENTS.md: found, but no applicable review policy affected this item.

Codex review notes: model internal, reasoning high; reviewed against c4f8978d922c.

Labels

Label changes:

  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🦞 diamond lobster.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The changed production owner is the default R2 client used by server upload storage; the PR supplies a real local HTTP-origin scenario through that path, reporting that incomplete headers time out after the fix while a 35-second progressing PUT completes through the configured wrapper.
  • remove status: ⏳ waiting on author: Current PR status label is status: 👀 ready for maintainer look.
  • remove rating: 🦐 gold shrimp: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.

Label justifications:

  • P2: This is a bounded R2 storage reliability fix affecting deployments that configure a wrapped default HTTP transport.
  • merge-risk: 🚨 compatibility: The default R2 client now deliberately subjects R2 traffic to process-wide transport wrappers instead of cloning or bypassing their behavior.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The changed production owner is the default R2 client used by server upload storage; the PR supplies a real local HTTP-origin scenario through that path, reporting that incomplete headers time out after the fix while a 35-second progressing PUT completes through the configured wrapper.
  • proof: sufficient: Contributor real behavior proof is sufficient. The changed production owner is the default R2 client used by server upload storage; the PR supplies a real local HTTP-origin scenario through that path, reporting that incomplete headers time out after the fix while a 35-second progressing PUT completes through the configured wrapper.

Evidence

What I checked:

  • Current-main defect: The base implementation returns a bare HTTP client when the configured default transport is a wrapper rather than *http.Transport, leaving that R2 request without a response-header timeout. (apps/api/internal/uploadstore/r2.go:86, c4f8978d922c)
  • Introduced repair: The PR delegates to the configured default transport, composes HTTP trace hooks, starts the timer only after a successful write, and cancels it before body handling. (apps/api/internal/uploadstore/r2_transport.go:15, 2522032521d5)
  • Production caller boundary: The server's upload-storage construction supplies no custom HTTP client, so R2 uses this default-client behavior; callers that supply R2Config.HTTPClient continue to own transport policy. (apps/api/cmd/clickclack/main.go:643, 2522032521d5)
  • Real HTTP coverage: The added network lifecycle test sends incomplete headers through the configured wrapper and separately verifies that a progressing PUT exceeding 30 seconds completes, matching the stated after-fix behavior. (apps/api/internal/uploadstore/r2_transport_test.go:99, 2522032521d5)
  • Feature history: R2 storage and its original outbound-operation bound both date to commits by steipete; the checked-out history is shallow at later line attribution, so this is routing context rather than an introduction claim for the current fallback lines. (apps/api/internal/uploadstore/r2.go:86, f7a10e989706)
  • Release status: The PR head is not contained in a local release tag, and v0.3.1's changelog lacks this R2 header-stall fix; the change remains necessary rather than already shipped. (CHANGELOG.md:3, 2522032521d5)

Likely related people:

  • steipete: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)
  • Shakker: 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-08-30T00:23:08.947Z sha 2576451 :: needs changes before merge. :: [P1] Preserve the wrapped default transport

Delegate through the configured default transport and compose its HTTP trace
hooks. Time only the complete-header wait after a successful request write,
with synchronized retry/phase cleanup and one response-body lifetime owner.
Keep explicitly supplied clients unchanged and total client timeout disabled.

Co-authored-by: Sebastien Tardif <sebtardif@ncf.ca>
@steipete steipete changed the title fix(uploadstore): never return a bare HTTP client for R2 fix(uploadstore): preserve default transport while bounding R2 headers Aug 30, 2026
@steipete

Copy link
Copy Markdown
Contributor

@clawsweeper re-review

The configured default transport is now delegated to, preserving wrappers while enforcing the after-write complete-header bound. The PR body includes fail-before/pass-after real HTTP evidence, a progressing PUT exceeding30s, and race/lifecycle coverage. The prior wrapper-bypass finding and rank-up move are addressed; supplied HTTP clients remain unchanged. Head2522032521d5e6d3482636b9f5dff5ee15dcdc5f.

@clawsweeper

clawsweeper Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Aug 30, 2026
@steipete

Copy link
Copy Markdown
Contributor

Maintainer review complete at 2522032. The documented default-transport contract is accepted: delegation preserves configured wrappers, and the precise after-write deadline requires the existing Go trace/cancellation lifecycle. Explicit clients keep their own policy. This addresses the remaining review risk without bypassing routing or adding a total timeout that would break progressing uploads.

Independent source and receipt review covered the shared lifetime owner, retry/phase races, cleanup, client contracts, and the real network proof: incomplete headers stopped at 30.002 seconds; a 35.082-second PUT completed through the configured wrapper. Full exact-head CI and all desktop jobs are green. The earlier wrapper-bypass finding and rank-up move are addressed; the fresh review has no actionable finding.

@steipete
steipete merged commit 2fd53fd into openclaw:main Aug 30, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants