Skip to content

fix(web): abort hung API and slash-command fetches - #168

Merged
steipete merged 2 commits into
openclaw:mainfrom
SebTardif:fix/web-fetch-timeout
Aug 16, 2026
Merged

fix(web): abort hung API and slash-command fetches#168
steipete merged 2 commits into
openclaw:mainfrom
SebTardif:fix/web-fetch-timeout

Conversation

@SebTardif

Copy link
Copy Markdown
Contributor

What Problem This Solves

Fixes an issue where users opening ClickClack (loading workspaces, sending a slash command, refreshing profile data) would see the UI spin forever when the API host accepted the TCP connection and then stalled. The browser fetch for /api/* and POST /api/hooks/slash/:channel had no AbortSignal, so the request never finished and the page never recovered.

This is the web JSON/slash client, not artifact preview (that path already passes a signal) and not the R2 upload client (its Timeout == 0 contract stays in place for streaming).

Why This Change Was Made

Both browser call sites now go through applyDefaultFetchTimeout, which attaches AbortSignal.timeout(30_000) when the caller did not already pass init.signal. Callers that need a longer or shorter bound can still supply their own signal. The embedded web bundle is rebuilt so the Go binary serves the same helper.

APIError is written with an explicit status field so Node's strip-only TypeScript loader can import api() from the existing node --test suite.

User Impact

If the API host hangs, workspace loads and slash commands fail after 30 seconds instead of leaving the composer or settings pane pending with no way out. Successful requests are unchanged. Operators who already pass a signal keep that signal.

Evidence

Live node against the patched apps/web/src/lib/api.ts and the embedded production chunk:

$ node --experimental-strip-types /tmp/proof-clickclack-fetch.mjs
defaultTimeoutMs=30000
before.hasSignal=false
after.hasSignal=true
after.abortedNow=false
hung slash-style fetch:
  name=AbortError
  message=The operation was aborted.
callerOverride.sameSignal=true
bundle.hasAbortSignalTimeout=true
bundle.has30sConstant=true

Compiled helper in apps/api/internal/webassets/dist/_app/immutable/chunks/DoZJIpeX.js:

$ grep -o 'var o=3e4;function s(e={}){return e.signal?e:{...e,signal:AbortSignal.timeout(o)}}' \
  apps/api/internal/webassets/dist/_app/immutable/chunks/DoZJIpeX.js
var o=3e4;function s(e={}){return e.signal?e:{...e,signal:AbortSignal.timeout(o)}}

Without a default signal, a stand-in fetch that never returns stays pending (before.hasSignal=false). After the patch the same stand-in is aborted (AbortError) and a caller-supplied signal is left in place.

Real behavior proof

  • Behavior or issue addressed: Browser API and slash-command fetches never aborted, so a stalled API host froze workspace loads and slash dispatch.

  • Real environment tested: macOS, Node v26.7.0, branch fix/web-fetch-timeout at /tmp/oc-impl-clickclack-fetch, patched web sources plus the rebuilt embedded chunk DoZJIpeX.js.

  • Exact steps or command run after this patch:

    cd /tmp/oc-impl-clickclack-fetch
    node --experimental-strip-types /tmp/proof-clickclack-fetch.mjs
    grep -n "AbortSignal.timeout" apps/api/internal/webassets/dist/_app/immutable/chunks/DoZJIpeX.js
  • Evidence after fix: terminal output from the patched tree:

    $ node --experimental-strip-types /tmp/proof-clickclack-fetch.mjs
    defaultTimeoutMs=30000
    before.hasSignal=false
    after.hasSignal=true
    after.abortedNow=false
    hung slash-style fetch:
      name=AbortError
      message=The operation was aborted.
    callerOverride.sameSignal=true
    bundle.hasAbortSignalTimeout=true
    bundle.has30sConstant=true
  • Observed result after fix: The default helper now attaches a 30s AbortSignal. A fetch that never returns is rejected with AbortError instead of hanging. A caller-provided signal is reused. The embedded production chunk contains AbortSignal.timeout and the 3e4 default.

  • What was not tested: A browser session against a real stalled production API host. Artifact preview fetch and the R2 Client.Timeout == 0 streaming path were left unchanged.

Related

Browser api() and slash-command dispatch used fetch with no
AbortSignal, so a stalled API host left the UI pending forever.

Apply AbortSignal.timeout(30s) unless the caller already provided
a signal. Keep R2 Client.Timeout at 0 (streaming-safe contract).

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@clawsweeper

clawsweeper Bot commented Aug 15, 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. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 15, 2026
@clawsweeper

clawsweeper Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed August 15, 2026, 10:53 PM ET / August 16, 2026, 02:53 UTC.

ClawSweeper review

What this changes

The PR applies a 30-second default abort signal to browser JSON API and slash-command requests while retaining caller signals and excluding multipart uploads.

Merge readiness

Blocked until real behavior proof from a real setup is added - 4 items remain

Keep open: the timeout approach is narrowly scoped, but merge still needs real browser-path proof and removal of the release-owned changelog entry. Likely related people: steipete (API client) and shakkernerd (slash dispatch).

Priority: P2
Reviewed head: aff2eef032849593ed8ddf6be6d8b51139d31c89

Review scores

Measure Result What it means
Overall readiness 🦪 silver shellfish (2/6) The implementation and focused tests are solid, but mocked timeout evidence does not yet meet the real browser-behavior merge gate.
Proof confidence 🦪 silver shellfish (2/6) Needs real behavior proof before merge: The terminal evidence and added tests use a stand-in hanging fetch with a shortened timeout; add redacted browser network or live-output evidence against a controlled stalled endpoint before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Patch quality 🐚 platinum hermit (4/6) 1 actionable review finding remain.

Verification

Check Result Evidence
Real behavior Needs proof Needs real behavior proof before merge: The terminal evidence and added tests use a stand-in hanging fetch with a shortened timeout; add redacted browser network or live-output evidence against a controlled stalled endpoint before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed 6 items Current main lacks a request abort boundary: The shared JSON helper passes request options directly to fetch without a signal, so a stalled API response can remain pending.
PR applies the shared timeout and preserves upload behavior: The branch attaches AbortSignal.timeout(30_000) only when no caller signal exists and skips FormData; both JSON API calls and the slash dispatcher use it.
Slash commands have a bounded server-side callback: Registered command callbacks use a three-second server HTTP timeout, making the proposed client-side 30-second bound compatible with the normal callback path.
Findings 1 actionable finding [P3] Leave CHANGELOG.md to release management
Security None None.

How this fits together

ClickClack’s browser app uses shared request helpers to load workspace data and dispatch registered slash commands to its API. The web bundle is embedded in the API service, so the generated assets must reflect the source helper.

flowchart LR
A[Workspace or composer] --> B[Web request helper]
B --> C{Signal supplied?}
C -->|Yes| D[Use caller signal]
C -->|No| E[Add 30 second timeout]
D --> F[ClickClack API]
E --> F
F --> G[Response or abort]
G --> H[UI recovery]
Loading

Before merge

  • Add real behavior proof - Needs real behavior proof before merge: The terminal evidence and added tests use a stand-in hanging fetch with a shortened timeout; add redacted browser network or live-output evidence against a controlled stalled endpoint before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
  • Leave CHANGELOG.md to release management (P3) - This PR adds an Unreleased entry, but release notes are release-owned in this repository. Remove this change and retain the user-impact context in the PR body or commit message.
  • Resolve merge risk (P2) - The supplied evidence exercises a mocked hanging fetch with a shortened timeout, not an after-fix browser request through a controlled stalled endpoint.
  • Complete next step (P2) - The contributor must supply real browser-path proof before merge; the remaining code review item is the release-owned changelog entry.

Findings

  • [P3] Leave CHANGELOG.md to release management — CHANGELOG.md:3-6
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Changed surface 30 files: 2 web-source files, 1 test file, 26 embedded web-asset files, 1 changelog Most of the diff is generated asset churn, so reviewers should verify that it corresponds only to the request-helper change.
Source versus test delta source +28/-16, tests +128, generated assets +43/-43 The implementation is small relative to its focused regression coverage.

Merge-risk options

Maintainer options:

  1. Decide the mitigation before merge
    Keep the shared default timeout, remove the release-owned changelog edit, and add redacted browser network or live-output proof showing a controlled stalled API request aborting and the UI recovering.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Technical review

Best possible solution:

Keep the shared default timeout, remove the release-owned changelog edit, and add redacted browser network or live-output proof showing a controlled stalled API request aborting and the UI recovering.

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

Yes, from source: current main’s shared JSON and slash-command fetches have no abort signal, and a controlled endpoint that accepts then stalls provides a high-confidence browser reproduction path.

Is this the best way to solve the issue?

Yes, conditionally: a shared default with caller-signal and multipart exceptions is the narrowest repair, but it needs real browser proof and should not modify release-owned changelog content.

Full review comments:

  • [P3] Leave CHANGELOG.md to release management — CHANGELOG.md:3-6
    This PR adds an Unreleased entry, but release notes are release-owned in this repository. Remove this change and retain the user-impact context in the PR body or commit message.
    Confidence: 0.99

Overall correctness: patch is correct
Overall confidence: 0.93

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

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

Labels

Label changes:

  • remove merge-risk: 🚨 compatibility: Current PR review selected no merge-risk labels.

Label justifications:

  • P2: This fixes a bounded browser workflow reliability failure without evidence of emergency-wide impact.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🐚 platinum hermit.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The terminal evidence and added tests use a stand-in hanging fetch with a shortened timeout; add redacted browser network or live-output evidence against a controlled stalled endpoint before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Evidence

What I checked:

  • Current main lacks a request abort boundary: The shared JSON helper passes request options directly to fetch without a signal, so a stalled API response can remain pending. (apps/web/src/lib/api.ts:50, 18acea79465c)
  • PR applies the shared timeout and preserves upload behavior: The branch attaches AbortSignal.timeout(30_000) only when no caller signal exists and skips FormData; both JSON API calls and the slash dispatcher use it. (apps/web/src/lib/api.ts:51, aff2eef03284)
  • Slash commands have a bounded server-side callback: Registered command callbacks use a three-second server HTTP timeout, making the proposed client-side 30-second bound compatible with the normal callback path. (apps/api/internal/httpapi/callback_http.go:12, 18acea79465c)
  • Slash dispatch already restores the draft after failure: The caller catches dispatch failures, displays an error notice, and restores the command text, so an abort reaches an existing recovery path. (apps/web/src/ChatApp.svelte:2391, aff2eef03284)
  • Feature provenance: The command dispatcher was introduced by a3c3ca6, while the API-base-aware request helper was introduced by the merged split-origin work in 04402c8. (apps/web/src/lib/commands.ts:46, a3c3ca6c20c4)
  • Release status: The latest release v0.3.1 is current main; the proposed head is not part of that release, so this remains an unshipped candidate fix. (18acea79465c)

Likely related people:

  • steipete: Merged split-origin work introduced the API-base-aware helper that this PR changes; recent main history also includes the release baseline. (role: introduced the current API request path; confidence: high; commits: 04402c823df1, 18acea79465c; files: apps/web/src/lib/api.ts)
  • shakkernerd: The command utility containing the dispatcher was introduced in the command-menu feature history. (role: introduced slash-command dispatch; confidence: high; commits: a3c3ca6c20c4, 6a718297d149; files: apps/web/src/lib/commands.ts)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Remove the release-owned CHANGELOG.md edit.
  • Add redacted browser network or live-output proof from a controlled stalled endpoint; redact private URLs, addresses, and credentials.

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-15T23:15:02.114Z sha 23ae91e :: needs real behavior proof before merge. :: none

@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. label Aug 16, 2026
@steipete
steipete merged commit cb40591 into openclaw:main Aug 16, 2026
3 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants