fix(setup): cancel node pairing cleanly instead of a spurious timeout warning + wasted retry#1011
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 17, 2026, 4:55 PM ET / 20:55 UTC. Summary Reproducibility: yes. with high confidence from source and the supplied red-to-green evidence: a loopback WebSocket can hold the real pairing wait, then caller cancellation exposes the prior retry-warning path and the corrected direct-cancellation path. The live gateway transcript corroborates that focused scenario. Review metrics: 2 noteworthy metrics.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Next step before merge
Security Review detailsBest possible solution: Merge the focused cancellation-propagation fix after ordinary maintainer review, retaining the pipeline-level regression contract and the genuine-timeout coverage. Do we have a high-confidence way to reproduce the issue? Yes, with high confidence from source and the supplied red-to-green evidence: a loopback WebSocket can hold the real pairing wait, then caller cancellation exposes the prior retry-warning path and the corrected direct-cancellation path. The live gateway transcript corroborates that focused scenario. Is this the best way to solve the issue? Yes. Separating caller cancellation from the linked internal timeout at the two existing catch boundaries is the narrowest maintainable fix, and the pipeline test protects the actual log, retry, exit-code, and journal contract. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 081f307a4561. Label changesLabel changes:
Label justifications:
Evidence reviewedWhat I checked:
Likely related people:
What the crustacean ranks mean
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. How this review workflow works
Review history (6 earlier review cycles)
|
The production fix is narrow and looks correct. The two cancellation-aware catch filters reuse the established The remaining work is proof and one pipeline-level assertion of the actual user-visible contract. Please add an integration test that drives the real
The current helper/step tests correctly prove exception propagation, but they can pass without proving the final retry/log/journal behavior the PR promises. Please also attach current-head evidence from the shipped Windows setup flow rather than only the loopback harness:
Test-quality notes:
With deterministic pipeline coverage and one real setup cancellation transcript, this should clear the landing bar without broadening the production change. |
294ef17 to
29ce432
Compare
|
Thanks — actioned the review (
On the real end-to-end setup transcript: I've added the deterministic pipeline coverage, but I don't currently have a live gateway-pairing environment to capture a transcript from the shipped setup flow — the run still substitutes a loopback WebSocket + fake WSL/reachability deps. Happy to add a real transcript if you can point me at a gateway env; otherwise I'll leave that to your discretion given the production change is two narrow catch filters. Body updated with the corrected scope + the new test breakdown. @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
… warning + wasted retry On a user cancel during node pairing, the step logged a misleading "Node connection failed: Timeout" and burned a retry (PairNodeStep has MaxAttempts=3) before the pipeline reported Cancelled — the final outcome is Cancelled either way, but via a spurious failure narrative and a wasted retry delay. WaitForNodeConnection caught every OperationCanceledException as Timeout, and PairNodeStep's catch-all re-swallowed it into StepResult.Fail. Guard the Timeout mapping with when (!ct.IsCancellationRequested) and add the file's existing cancel-rethrow idiom (SetupSteps.cs:1725, :2044) to PairNodeStep, so a caller cancel propagates and the pipeline maps it to Cancelled immediately — no failure warning, no retry. Tests (SetupEngine.Tests), all red on base / green on HEAD except the timeout guard: - SetupPipeline_CallerCancel_CancelsCleanlyWithoutFailureNarrative: drives the REAL pipeline and asserts the full contract — Cancelled/exit 3, no 'connection failed'/retry warning, pipeline_cancelled journal (not a failed-step narrative). - PairNodeStep_CallerCancellation + WaitForNodeConnection_CallerCancellation: the two catch boundaries, cancelled from a deterministic WS-upgrade barrier (no timing guess). - WaitForNodeConnection_GenuineInternalTimeout: a real internal timeout still maps to Timeout.
29ce432 to
55f93df
Compare
|
Added the real-gateway transcript you asked for (head I drove the real
So on the real gateway the fix removes the misleading Also cleaned up a slip from an earlier amend (removed two accidentally-committed @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Thanks for the thorough review — everything requested is now in at 1. Pipeline integration test asserting all three outcomes together — added 2. Real setup-flow transcript — in the PR body's Real Behavior Proof. I drove the real
Honest caveat: only the pre-step WSL approval-drain is stubbed in the harness; the gateway WebSocket connection, the pairing wait, and the cancellation are all real. 3. Genuine timeout unchanged — 4. Test-quality notes — done: the 300 ms delay is replaced with a deterministic The production change is still only the two catch filters. Happy to adjust anything further. |
Related: #1010
What Problem This Solves
When a user cancels node setup while the pairing step is waiting to connect, the cancellation isn't handled cleanly: the step logs a misleading
"Node connection failed: Timeout"warning (implying a connection problem, not a user abort), then burns a retry —Task.Delay(3s, ct)on the pairing step's retry policy — before the cancellation finally surfaces. The pipeline does end upCancelledeither way, but only after a spurious error and a wasted retry delay, and the journal/log record a connection "failure" that never happened.Why This Change Was Made
WaitForNodeConnectionlinks the caller's token with an internalCancelAfter(timeout)and catches everyOperationCanceledExceptionasNodeConnectionOutcome.Timeout;PairNodeStep.ExecuteAsync's catch-all then turns that intoStepResult.Fail(...). So a user cancel is reported as a failed connection attempt, which the step's retry policy (MaxAttempts: 3) then retries — and only the retry'sTask.Delay(..., ct)tripping on the cancelled token gets the pipeline toCancelled.The fix makes the cancellation propagate directly, using the file's existing cancel-rethrow idiom (
SetupSteps.cs:1725,:2044): guard theTimeoutmapping withwhen (!ct.IsCancellationRequested), and addcatch (OperationCanceledException) when (ct.IsCancellationRequested) { throw; }ahead ofPairNodeStep's catch-all. The pipeline then maps the propagated OCE straight toCancelled— no spurious "failed" log, no wasted retry. The genuine-timeout path is unchanged.Scope — what this does and doesn't change (traced end-to-end)
Being precise, because it's easy to overstate: the final pipeline outcome is
Cancelled(exit 3) with or without this change —PairNodeStep's retry policy means a caller-cancel trips the retry delay and surfaces asCancelledregardless. What this PR changes is the path to that outcome on a user cancel:[Warn] Step 'pair-node' failed (attempt 1/3), retrying in 3s: Node connection failed: Timeout→ (retry delay) →Cancelled.Cancelled, no spurious warning, no retry delay.So this is a cancellation-cleanliness / accurate-diagnostics fix, not an outcome change.
User Impact
Cancelling node setup no longer emits a misleading "connection failed" warning to the log/journal or wastes a retry cycle — the abort is recorded and surfaced accurately and immediately. No change for a real connection timeout.
Change Type
Scope
winnodeValidation
Full Windows CI-matrix mirror (win-x64, .NET 10.0.301), HEAD
55f93df0vs base9fb1960e— every project built and all 8 suites passed. Four focused regression tests (SetupEngine.Tests), on base vs HEAD:SetupPipeline_CallerCancel_CancelsCleanlyWithoutFailureNarrative— the durable end-to-end contract: drives the realSetupPipelinewith the realPairNodeStepand asserts all three of Cancelled/exit 3, no"Node connection failed"/retry warning, and apipeline_cancelledjournal entry (not a failed-step narrative). This is what actually pins the fix — it's red on base (the retry-warning assertion fails) and green on HEAD.PairNodeStep_CallerCancellation+WaitForNodeConnection_CallerCancellation— the two catch boundaries, cancelled from a deterministic WebSocket-upgrade barrier (no timing guess).WaitForNodeConnection_GenuineInternalTimeout— a real internal timeout still maps toTimeout(passes on both base and HEAD; proves the fix only diverts caller cancels).This revision addresses the ClawSweeper / Copilot maintainer-assistant review: adds the deterministic pipeline-level contract, the genuine-timeout assertion, the upgrade barrier (replacing the 300 ms delay), and binds the reflection lookup by full signature with a clear failure message.
(Two intermittent pre-existing flakes exist in the full matrix —
Connection.Tests.ConnectAndReconnect_EmitCompletedOperatorSpansandTray.UITests.ChatTimelineVirtualizationProofTests— on surfaces this SetupEngine-only diff doesn't touch; neither is a regression here.)Real Behavior Proof
Real gateway transcript
Driving the real
SetupPipeline/PairNodeStepagainst a live OpenClaw gateway (a genuineconnect.challengehandshake — node role, 9 capabilities, 30 commands, v2 signature), then cancelling while node pairing is waiting — the sameCancellationTokenthe setup CLI'sConsole.CancelKeyPress(Ctrl+C) cancels. Base vs HEAD:55f93df0) — clean immediate cancellation, no warning, no retry:Same final outcome; on HEAD there is no
"Node connection failed"warning and no retry. Caveat: the pre-step WSL approval-drain is stubbed in this harness; the gateway WebSocket connection, the pairing wait, and the cancellation are all real.Deterministic pipeline coverage (in CI)
The same contract is asserted deterministically by
SetupPipeline_CallerCancel_CancelsCleanlyWithoutFailureNarrative(loopback WS + upgrade barrier, no live gateway needed): Cancelled/exit 3, no"Node connection failed"/retry warning,pipeline_cancelledjournal — red on base, green on HEAD.ws://127.0.0.1:18789)55f93df0/ (fix hunks reverted)N/A(non-UI setup/connection logic; proof is the real-gateway transcript + the paired red→green tests)Security Impact
NoNoNoNoNoCompatibility and Migration
YesNoNoReview History
Local review history (findings + dispositions per commit), including the correction from the original "reports Failed" framing to the accurate cancellation-cleanliness scope: https://gist.github.com/anagnorisis2peripeteia/0bdbede425c116811993f71fed9816b9
Review Conversations