fix(gateway): mark SIGUSR1 token consumed on restartIntent path, reset stale tokens on in-process restart#84334
Conversation
…t stale tokens on in-process restart In onSigusr1, when a restartIntent file is present the handler calls request() and returns without calling markGatewaySigusr1RestartHandled(). This leaves emittedRestartToken > consumedRestartToken for the lifetime of the process. Every subsequent scheduleGatewaySigusr1Restart() call hits the hasUnconsumedRestartSignal() guard and is silently coalesced, making the update.run restart permanently stuck. Fix 1 (root cause): call markGatewaySigusr1RestartHandled() before the restartIntent early-return in onSigusr1 so the token is consumed regardless of which restart path is taken. Fix 2 (safety net): align the token counters in resetGatewayRestartStateForInProcessRestart() so a stale unconsumed token from a previous lifecycle cannot carry forward into the next server iteration after an in-process restart.
|
Thanks for the context here. I did a careful shell check against current Close as implemented: current main and shipped releases already solve the observable Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. So I’m closing this as already implemented rather than keeping a duplicate issue open. Review detailsBest possible solution: Keep the shipped lifecycle-runtime restart fix and close this conflicting branch; open a new narrow current-main PR only if maintainers want the extra reset-boundary hardening. Do we have a high-confidence way to reproduce the issue? No current-main failing reproduction remains for the original Is this the best way to solve the issue? No, this branch is no longer the best way to solve the issue. The best path is the shipped current-main restart lifecycle fix, with any extra reset-boundary guard proposed separately against today's SIGUSR1 authorization flow. Security review: Security review cleared: The diff only touches gateway restart state and tests; no dependency, workflow, secret, package, or third-party code execution change was found. AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against 640258d7b31d; fix evidence: release v2026.5.22, commit 4a9138556ea6. |
- run-loop.test.ts: assert markGatewaySigusr1RestartHandled is called before request() in the restartIntent branch of onSigusr1; both call count and invocation order are verified. Uses waitForLoopCondition for reliable async completion rather than fixed setImmediate counts. - infra-runtime.test.ts: assert resetGatewayRestartStateForInProcessRestart aligns stale emit/consume token counters so a post-reset restart request is not coalesced. Both tests fail against current main and pass with the patch. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
ClawSweeper PR egg 🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat. Where did the egg go?
|
|
Note on the Producing after-fix logs requires the patched gateway to be in a released version that an end user can What's in the PR instead:
The workflow output notes that a maintainer can apply a |
|
ClawSweeper applied the proposed close for this PR.
|
Summary
update.runfrom the control UI downloads the new package but the gateway never restarts to pick it up. Every attempt logsrestart coalesced (already in-flight)and is silently dropped for the lifetime of the process.Root cause
Two bugs combine to make the state permanent:
Bug 1 —
src/cli/gateway-cli/run-loop.ts(onSigusr1):When a
restartIntentfile is present, the handler callsrequest()and returns without callingmarkGatewaySigusr1RestartHandled(). This leavesemittedRestartToken > consumedRestartToken. Every subsequentscheduleGatewaySigusr1Restart()call hits thehasUnconsumedRestartSignal()guard and is dropped.Bug 2 —
src/infra/restart.ts(resetGatewayRestartStateForInProcessRestart):This function clears timers and deferral polls but never aligns the token counters. A stale unconsumed token from the previous lifecycle carries forward into the next server iteration after an in-process restart.
Fix
markGatewaySigusr1RestartHandled()before therestartIntentearly-return inonSigusr1so the token is consumed regardless of which restart path is taken.consumedRestartToken = emittedRestartTokeninresetGatewayRestartStateForInProcessRestart()as a safety net so a stale token cannot survive an in-process restart boundary.Reproduction
update.run(UI or CLI)restart coalesced (already in-flight)Workaround (until patched)
Real behavior log
Platform: systemd user service (
openclaw-gateway.service), Ubuntu 24.04 LTS / WSL2Two
update.runattempts 11 minutes apart.changedPaths=<n/a>on the second attempt confirms the npm package updated successfully on attempt 1; only the restart was broken. Gateway process<pid-a>never changed between attempts.Regression tests
Two focused tests cover the two fixes and are included in this PR:
src/cli/gateway-cli/run-loop.test.ts: "marks the in-flight token consumed before routing SIGUSR1 with a restart intent" asserts thatmarkGatewaySigusr1RestartHandledis called beforerequest()is routed in therestartIntentbranch. Fails on current main, passes with the fix.src/infra/infra-runtime.test.ts: "aligns stale emit/consume token counters on in-process restart boundary" asserts thatresetGatewayRestartStateForInProcessRestart()clears a stale unconsumed token so subsequent restart requests are not coalesced. Fails on current main, passes with the fix.Post-patch note
Post-patch behavior is validated by the regression tests added in this PR; both fail against current main and pass with the patch applied. End-to-end log proof of
update.runsucceeding post-patch is omitted because reproducing it requires cutting a new release for the patched gateway to pull from, which is outside contributor scope.