Skip to content

fix(review): time out hung git fetch and state-blob requests - #1175

Open
SebTardif wants to merge 1 commit into
openclaw:mainfrom
SebTardif:fix/review-io-timeouts
Open

fix(review): time out hung git fetch and state-blob requests#1175
SebTardif wants to merge 1 commit into
openclaw:mainfrom
SebTardif:fix/review-io-timeouts

Conversation

@SebTardif

Copy link
Copy Markdown

What Problem This Solves

Fixes an issue where a review worker could hang forever on two I/O paths. Blob hydration runs git fetch origin --stdin through spawnSync with no timeout, so a stalled origin never returns. The state-blob client retries a Worker POST up to four times with no AbortSignal, so a hung Worker blocks each attempt without a deadline.

Why This Change Was Made

Review blob hydration now gives every git spawnSync a deadline: 180s for the network fetch, 60s for local ls-tree / rev-list / cat-file. The existing hydrated: false path still applies when Node returns ETIMEDOUT. State-blob POSTs now pass signal: AbortSignal.timeout(15000) on each attempt and keep the current four-attempt backoff. Media-proof ffmpeg/ffprobe stays on its own PR.

User Impact

A stalled git origin or Worker no longer pins a review worker until an outer job timeout. Hydration fails closed (hydrated: false). A hung blob publish fails after four timed attempts instead of blocking the process.

OpenClaw Bay Impact

Unaffected. This change is review-worker I/O deadlines only. It does not alter Bay lifecycle, queue, status, telemetry, or dashboard data contracts.

Documentation Lifecycle

No documentation lifecycle changes.

Evidence

Before this change, src/clawsweeper-review-blobs.ts passed cwd / encoding / maxBuffer to git fetch with no timeout, and src/state-blob-client.ts called fetch with no signal. After the change, a live node import of the compiled helpers prints fetch 180000 / local 60000. sleep 30 with spawnSync timeout 80ms returns ETIMEDOUT in 82ms. AbortSignal.timeout(80) fires TimeoutError. A hung state-blob fetchImpl that waits on signal.abort is called four times with requestedMs=15000 and rejects with The operation was aborted. in 1713ms.

$ node io-timeout-proof.mjs
review blob git timeouts:
{
  "fetchMs": 180000,
  "localMs": 60000
}

hung sleep 30 with spawnSync timeout 80ms:
{
  "status": null,
  "signal": "SIGTERM",
  "error": {
    "name": "Error",
    "message": "spawnSync sleep ETIMEDOUT",
    "code": "ETIMEDOUT"
  },
  "elapsedMs": 82
}

AbortSignal.timeout(80) abort:
{
  "aborted": true,
  "reasonName": "TimeoutError",
  "reasonMessage": "The operation was aborted due to timeout",
  "elapsedMs": 82
}
AbortSignal.timeout requestedMs=15000
AbortSignal.timeout requestedMs=15000
AbortSignal.timeout requestedMs=15000
AbortSignal.timeout requestedMs=15000

hung state-blob fetch after patch:
{
  "attempts": 4,
  "message": "The operation was aborted.",
  "elapsedMs": 1713
}

Related: unbounded git fetch landed in #982 (2026-07-31). State-blob POST without a signal landed in #936 (2026-07-29). Repair git helpers already have this bound in #508. Media-proof spawn timeout is a separate surface in #1173. Sibling Worker POSTs already use AbortSignal.timeout (5s to 20s).

Real behavior proof

  • Behavior or issue addressed: Review-worker blob hydration could hang forever on git fetch origin --stdin because spawnSync had no timeout. State-blob publish could hang forever on each Worker POST because fetch had no AbortSignal.

  • Real environment tested: macOS, Node v26.7.0, worktree /tmp/oc-impl-clawsweeper-io-timeout at the patched commit.

  • Exact steps or command run after this patch:

    node io-timeout-proof.mjs

    The script imported reviewBlobGitSpawnTimeoutsForTest and publishStateBlob from the compiled helpers, ran spawnSync("sleep", ["30"], { timeout: 80 }), waited on AbortSignal.timeout(80), then called publishStateBlob with a fetchImpl that never resolves and only rejects when init.signal aborts. The client still requested AbortSignal.timeout(15000) per attempt; the script shortened that timer to 50ms so the hang path could be observed.

  • Evidence after fix: terminal output from the live node command above. Compiled review-blob timeouts are fetchMs: 180000 and localMs: 60000. Hung sleep 30 returns status: null, signal: SIGTERM, error.code: ETIMEDOUT in 82ms. AbortSignal.timeout(80) reports TimeoutError / The operation was aborted due to timeout. The hung Worker POST is attempted four times with requestedMs=15000 and ends with The operation was aborted. in 1713ms.

  • Observed result after fix: Node kills the hung sleep instead of waiting 30 seconds. The state-blob client no longer waits on a fetch that never settles. It aborts each attempt and keeps the existing four-attempt backoff, then throws.

  • What was not tested: A live hung git fetch against a real origin and a live hung Cloudflare Worker. Media-proof ffmpeg/ffprobe remains on fix(media-proof): time out hung ffmpeg and ffprobe #1173.

Keep Allow edits from maintainers enabled.

Review blob hydration's origin fetch used spawnSync without a timeout,
so a stalled git remote blocked the review worker. State blob POST
retries used fetch without AbortSignal, so a hung Worker blocked each
attempt. Bound git fetch to 180s (local git to 60s) and each blob
request to 15s while keeping the existing four-attempt backoff.

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: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. 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 24, 2026, 4:12 AM ET / 08:12 UTC.

ClawSweeper review

What this changes

The PR adds deadlines to review-blob Git commands and state-blob Worker POST retries, with focused timeout tests.

Regression provenance

Possible regression — suspected (reviewed change). No predecessor PR is attributed.

Merge readiness

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

Keep open: the focused timeout fix remains useful, but the branch conflicts with current main and does not cover the newer exact-checkout Git fetch path, so its central availability claim is incomplete.

Priority: P2
Reviewed head: 9f1a93e928058ed7e9273f72bac00a9f13ec2d0b

Review scores

Measure Result What it means
Overall readiness 🦪 silver shellfish (2/6) The patch is focused, but it conflicts with current main, misses a newer Git fetch path, and has mock-only behavior proof.
Proof confidence 🦪 silver shellfish (2/6) Needs real behavior proof before merge: The terminal transcript checks timeout primitives and an injected fetch, not the actual current Git checkout/hydration or state-blob transport boundaries; add redacted after-fix boundary evidence 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 🦐 gold shrimp (3/6) 1 actionable review finding remain.

Verification

Check Result Evidence
Real behavior Needs proof Needs real behavior proof before merge: The terminal transcript checks timeout primitives and an injected fetch, not the actual current Git checkout/hydration or state-blob transport boundaries; add redacted after-fix boundary evidence 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 checkout fetch remains unbounded: Current main's exact-commit checkout helper calls git fetch without a timeout before blob hydration; resolving this PR's conflicts without extending its helper leaves a review-worker hang path.
State-blob request is still unbounded on main: Current main's signed POST invokes the fetch implementation without a signal, so the state-blob portion of the PR addresses a real remaining path.
Current-main checkout behavior postdates the branch: Commit ad5cf51 introduced the exact-head checkout helpers in the same review-blob module after this PR's base; it is the source of the merge conflict and the missing timeout coverage.
Findings 1 actionable finding [P1] Cover the current review-checkout fetch
Security None None.

Live Verification

Command: pnpm run test:unit -- test/review-blob-hydration.test.ts test/state-blob-client.test.ts

Result: FAIL (partial) — step 2 expect_output state blob client aborts a hung fetch after the per-attempt timeout: expected terminal output was not visible within 30 seconds: "state blob client aborts a hung fetch after the per-attempt timeout"

  url: 'file:///tmp/clawsweeper-live-proof-1175-GAWMQ6/target/dist/repair/canonical-record-baseline.js'
}

Node.js v24.19.0
✖ test/dashboard-worker-publication-lifecycle.test.ts (583.638436ms)
node:internal/modules/esm/resolve:271
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/clawsweeper-live-proof-1175-GAWMQ6/target/dist/repair/canonical-record-baseline.js' imported from /tmp/cl
awsweeper-live-proof-1175-GAWMQ6/target/test/dashboard-worker-harness.ts
    at finalizeResolution (node:internal/modules/esm/resolve:271:11)
    at moduleResolve (node:internal/modules/esm/resolve:865:10)
    at defaultResolve (node:internal/modules/esm/resolve:992:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:701:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:721:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:759:56)
    at #resolve (node:internal/modules/esm/loader:683:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:603:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:163:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:253:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///tmp/clawsweeper-live-proof-1175-GAWMQ6/target/dist/repair/canonical-record-baseline.js'
}

Node.js v24.19.0
✖ test/dashboard-worker-queue-policy.test.ts (618.229155ms)
node:internal/modules/esm/resolve:271
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/clawsweeper-live-proof-1175-GAWMQ6/target/dist/repair/canonical-record-baseline.js' imported from /tmp/cl
awsweeper-live-proof-1175-GAWMQ6/target/test/dashboard-worker-harness.ts
    at finalizeResolution (node:internal/modules/esm/resolve:271:11)
    at moduleResolve (node:internal/modules/esm/resolve:865:10)
    at defaultResolve (node:internal/modules/esm/resolve:992:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:701:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:721:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:759:56)
    at #resolve (node:internal/modules/esm/loader:683:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:603:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:163:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:253:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///tmp/clawsweeper-live-proof-1175-GAWMQ6/target/dist/repair/canonical-record-baseline.js'
}

Node.js v24.19.0
✖ test/dashboard-worker-queue-runtime.test.ts (591.576765ms)
node:internal/modules/esm/resolve:271
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/clawsweeper-live-proof-1175-GAWMQ6/target/dist/repair/canonical-record-baseline.js' imported from /tmp/cl
awsweeper-live-proof-1175-GAWMQ6/target/test/dashboard-worker-harness.ts
    at finalizeResolution (node:internal/modules/esm/resolve:271:11)
    at moduleResolve (node:internal/modules/esm/resolve:865:10)
    at defaultResolve (node:internal/modules/esm/resolve:992:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:701:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:721:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:759:56)
    at #resolve (node:internal/modules/esm/loader:683:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:603:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:163:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:253:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///tmp/clawsweeper-live-proof-1175-GAWMQ6/target/dist/repair/canonical-record-baseline.js'
}

Node.js v24.19.0
✖ test/dashboard-worker-telemetry-contracts.test.ts (623.34173ms)
node:internal/modules/esm/resolv
… output truncated …

Assertions:

  • FAIL expect_output: state blob client aborts a hung fetch after the per-attempt timeout

How this fits together

Review workers first obtain the exact pull-request commits and hydrate selected Git blobs before inspection, then publish signed state blobs to the canonical Worker/R2 storage path. A hang at either I/O boundary can hold a review worker until an outer timeout.

flowchart LR
  Review[Review worker] --> Checkout[Exact commit checkout]
  Checkout --> GitFetch[Git fetch]
  GitFetch --> Hydration[Changed-blob hydration]
  Hydration --> Inspection[Restricted review inspection]
  Review --> Publisher[Signed state-blob publisher]
  Publisher --> Worker[Canonical Worker and R2]
Loading

Before merge

  • Add real behavior proof - Needs real behavior proof before merge: The terminal transcript checks timeout primitives and an injected fetch, not the actual current Git checkout/hydration or state-blob transport boundaries; add redacted after-fix boundary evidence 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.
  • Cover the current review-checkout fetch (P1) - After rebasing, route the exact-commit checkout fetch introduced by fix(review): verify checkout access before publication #1170 through the timeout policy and add its stall regression. The branch predates that helper, so resolving the conflict as-is leaves a review-worker git fetch able to hang indefinitely before hydration runs.
  • Resolve merge risk (P1) - A simple conflict resolution would retain the newer exact-checkout git fetch without a deadline, leaving the reported worker-hang class reachable.
  • Resolve merge risk (P1) - The current terminal evidence uses a standalone child process and injected fetch implementation, so it does not prove failure recovery at either current production boundary.
  • Complete next step (P2) - The contributor must rebase, address the current-main timeout gap, and supply real behavior proof; that evidence cannot be produced by the repair lane.

Findings

  • [P1] Cover the current review-checkout fetch — src/clawsweeper-review-blobs.ts:8-21
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Implementation versus tests production +35/-24; tests +65 The patch is narrowly scoped and carries more regression coverage than production growth.

Merge-risk options

Maintainer options:

  1. Rebase and cover every review Git fetch (recommended)
    Resolve the current-main conflict, give the exact-checkout and hydration fetch paths bounded deadlines, and prove their controlled failure behavior before merge.

Technical review

Best possible solution:

Rebase onto current main, preserve the exact-head safeguards from #1170, apply bounded timeouts to both checkout and hydration network fetches, and provide redacted real-boundary recovery proof.

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

Yes: current-main source shows both the unsigned deadline-free state-blob fetch and the later exact-checkout Git fetch without a timeout, although this read-only review did not execute a live stall.

Is this the best way to solve the issue?

No: the state-blob change is directionally correct, but the branch must cover the newer current-main checkout fetch and be rebased before it solves the worker-hang class comprehensively.

Full review comments:

  • [P1] Cover the current review-checkout fetch — src/clawsweeper-review-blobs.ts:8-21
    After rebasing, route the exact-commit checkout fetch introduced by fix(review): verify checkout access before publication #1170 through the timeout policy and add its stall regression. The branch predates that helper, so resolving the conflict as-is leaves a review-worker git fetch able to hang indefinitely before hydration runs.
    Confidence: 0.98

Overall correctness: patch is incorrect
Overall confidence: 0.98

AGENTS.md: found and applied where relevant.

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

Labels

Label justifications:

  • P2: The item addresses a bounded review-worker availability failure rather than a user-facing runtime outage.
  • merge-risk: 🚨 availability: New deadlines can change slow Git or Worker operations from waiting to failed review-state operations.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The terminal transcript checks timeout primitives and an injected fetch, not the actual current Git checkout/hydration or state-blob transport boundaries; add redacted after-fix boundary evidence 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 checkout fetch remains unbounded: Current main's exact-commit checkout helper calls git fetch without a timeout before blob hydration; resolving this PR's conflicts without extending its helper leaves a review-worker hang path. (src/clawsweeper-review-blobs.ts:43, ce250708c1ea)
  • State-blob request is still unbounded on main: Current main's signed POST invokes the fetch implementation without a signal, so the state-blob portion of the PR addresses a real remaining path. (src/state-blob-client.ts:94, ce250708c1ea)
  • Current-main checkout behavior postdates the branch: Commit ad5cf51 introduced the exact-head checkout helpers in the same review-blob module after this PR's base; it is the source of the merge conflict and the missing timeout coverage. (src/clawsweeper-review-blobs.ts:30, ad5cf512a448)
  • Three-way merge is conflicted: The branch and current main both modify review-blob implementation and its test imports; GitHub likewise reports the PR as dirty. (src/clawsweeper-review-blobs.ts, 9f1a93e92805)
  • Submitted proof is mock-boundary evidence: The PR body demonstrates Node timeout primitives and an injected fetch implementation, but expressly does not exercise a real stalled Git transport or Worker endpoint through the current production paths.
  • Release check: Neither the newer exact-checkout commit nor this PR head is contained by a local release tag; latest provided release v0.3.0 therefore does not establish that the requested fix is shipped. (ad5cf512a448)

Likely related people:

  • jesse-merhi: Introduced the current-main exact pull-request checkout helpers that need equivalent timeout coverage after rebase. (role: introduced current checkout behavior; confidence: high; commits: ad5cf512a448; files: src/clawsweeper-review-blobs.ts, src/clawsweeper-context-hydration.ts, test/review-blob-hydration.test.ts)
  • Martin Cleary: Current-main blame attributes nearby review-blob and state-blob lines to a recent repository change, though the deeper state-client provenance is partially obscured by the checkout history. (role: recent current-main contributor; confidence: medium; commits: 41d88523dfe0; files: src/clawsweeper-review-blobs.ts, src/state-blob-client.ts)

Rank-up moves

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

  • Rebase and apply the timeout policy to the current exact-checkout and hydration fetch paths, with a controlled-stall regression test.
  • Post redacted terminal or trace evidence using a real Git transport and real state-blob transport client; then update the PR body for a fresh review.

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-15T21:37:38.107Z sha 9f1a93e :: needs real behavior proof before merge. :: none

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

Labels

merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. 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.

1 participant