Skip to content

fix(agent-core): stop canceled parallel tools from starting#102276

Merged
steipete merged 4 commits into
openclaw:mainfrom
harjothkhara:codex/102252-parallel-tool-abort
Jul 9, 2026
Merged

fix(agent-core): stop canceled parallel tools from starting#102276
steipete merged 4 commits into
openclaw:mainfrom
harjothkhara:codex/102252-parallel-tool-abort

Conversation

@harjothkhara

@harjothkhara harjothkhara commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Closes #102252

What Problem This Solves

Fixes an issue where users canceling a run during a parallel tool batch could still have an already-prepared paid tool start afterward when another tool was still in its pre-execution hook.

Why This Change Was Made

The agent loop now re-checks the abort signal immediately before a prepared tool is allowed to execute. If the run was canceled after preparation but before execution, the prepared call is finalized as aborted with executionStarted: false, and post-tool hooks are not invoked for work that never actually started.

This keeps the cancellation boundary in agent-core instead of relying on every individual paid tool to honor abort signals correctly.

AI-assisted implementation.

User Impact

Canceling a run during parallel tool preparation no longer starts stale prepared tool work afterward. This prevents unnecessary provider calls for tools that may dispatch paid jobs when execute begins.

Evidence

  • Current head: 231812d76644c1908b5aa8a4b1de298293e863b6 (fix(agent-core): skip prepared tools after abort).
  • Focused regression proof: bundled Node scripts/run-vitest.mjs packages/agent-core/src/agent-loop.test.ts -- --reporter=verbose passed: 1 file, 23 tests.
  • The new regression drives real runAgentLoop with two parallel tool calls, aborts from the second call's beforeToolCall, and asserts the already-prepared first call never executes, emits executionStarted: false, and does not run afterToolCall.
  • Post-fix real behavior proof outside Vitest: from the PR source checkout at 231812d76644c1908b5aa8a4b1de298293e863b6, I ran a standalone tsx harness that imports the source checkout's real packages/agent-core/src/agent-loop.ts and createAssistantMessageEventStream, then drives the canceled parallel batch directly. The paid_style_tool is a local paid-boundary stub: if paid-style dispatch begins, its execute counter increments.
$ node --import tsx /private/tmp/openclaw-102276-proof.mts
OPENCLAW_102276_REAL_BEHAVIOR_PROOF
{
  "scenario": "source-checkout runAgentLoop parallel batch abort, outside Vitest",
  "runAborted": true,
  "paidStyleToolExecuteInvocations": 0,
  "gatedToolExecuteInvocations": 0,
  "afterToolCallInvocations": 0,
  "toolExecutionEnd": [
    {
      "toolName": "gated_tool",
      "isError": true,
      "executionStarted": false,
      "text": [
        "Operation aborted"
      ]
    },
    {
      "toolName": "paid_style_tool",
      "isError": true,
      "executionStarted": false,
      "text": [
        "Operation aborted"
      ]
    }
  ],
  "finalMessage": {
    "role": "assistant",
    "content": [
      {
        "type": "text",
        "text": ""
      }
    ],
    "api": "proof-api",
    "provider": "proof-provider",
    "model": "proof-model",
    "usage": {
      "input": 0,
      "output": 0,
      "cacheRead": 0,
      "cacheWrite": 0,
      "totalTokens": 0,
      "cost": {
        "input": 0,
        "output": 0,
        "cacheRead": 0,
        "cacheWrite": 0,
        "total": 0
      }
    },
    "stopReason": "aborted",
    "errorMessage": "proof user abort",
    "timestamp": 1783540162496
  }
}
PASS=true
  • The real-behavior output shows the canceled parallel batch does not invoke the already-prepared paid-style tool: paidStyleToolExecuteInvocations is 0, afterToolCallInvocations is 0, and both finalized tool calls have executionStarted: false.
  • Whitespace proof: git diff --check and git diff --cached --check passed.
  • Structured review: bundled Python .agents/skills/autoreview/scripts/autoreview --mode local passed clean with no accepted/actionable findings.

@clawsweeper

clawsweeper Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 8, 2026, 4:01 PM ET / 20:01 UTC.

Summary
The branch adds a pre-execute abort check for prepared agent-core tool calls, carries executionStarted through skipped calls, skips afterToolCall for work that never started, and adds a regression test.

PR surface: Source +10, Tests +77. Total +87 across 2 files.

Reproducibility: yes. at source level. Current main defers prepared parallel calls through Promise.all and lacks a pre-execute abort guard, while the linked issue and PR test exercise runAgentLoop aborting during beforeToolCall; I did not run the repro in this read-only review.

Review metrics: 1 noteworthy metric.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #102252
Summary: This PR is a proof-positive candidate fix for the canonical prepared parallel-tool abort race, with one duplicate open candidate and one adjacent merged abort-loop fix.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Risk before merge

  • [P1] A second open PR targets the same canonical issue, so maintainers should choose one landing path and close the other after a fix merges.
  • [P1] The proof uses a paid-style local stub instead of billing a live paid provider, which is appropriate for this bug but leaves live-provider validation indirect.

Maintainer options:

  1. Decide the mitigation before merge
    Land one narrow agent-core pre-execute abort guard with regression coverage, preferably this proof-positive branch unless maintainers intentionally choose the duplicate candidate.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P2] No automated code repair is needed; maintainers need to choose between duplicate candidate PRs and land one fix path.

Maintainer decision needed

  • Question: Which candidate fix should maintainers land for the prepared parallel-tool abort race tracked by agent-core: parallel batch runs a prepared paid tool after the run is aborted mid-batch #102252?
  • Rationale: Two open PRs close the same canonical issue and touch the same agent-core boundary; this PR has sufficient terminal proof while the duplicate still lacks comparable proof, and merging both would create duplicate churn.
  • Likely owner: vincentkoc — vincentkoc is tied to adjacent abort-loop review/co-author history and recent agent-core path work, making them a good routing owner for the duplicate-fix choice.
  • Options:
    • Land this proof-positive PR (recommended): Proceed with maintainer merge review for this branch, then close the duplicate candidate and linked issue after one fix lands.
    • Use the duplicate candidate instead: Keep this PR open only until fix(agent-core): skip prepared tool execution after abort #102283 supplies comparable proof and maintainers intentionally choose that branch.
    • Pause both candidates: Hold both PRs if maintainers want a different cancellation architecture than the shared executePreparedToolCall guard.

Security
Cleared: The diff only changes internal agent-core cancellation logic and colocated tests; it adds no dependency, workflow, secret, install, publishing, or supply-chain surface.

Review details

Best possible solution:

Land one narrow agent-core pre-execute abort guard with regression coverage, preferably this proof-positive branch unless maintainers intentionally choose the duplicate candidate.

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

Yes, at source level. Current main defers prepared parallel calls through Promise.all and lacks a pre-execute abort guard, while the linked issue and PR test exercise runAgentLoop aborting during beforeToolCall; I did not run the repro in this read-only review.

Is this the best way to solve the issue?

Yes. The shared pre-execute guard in executePreparedToolCall is the narrowest owner-boundary fix because it stops any prepared tool before side effects begin, instead of relying on each paid tool to honor an already-aborted signal.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P1: The PR fixes a cancellation race where a user abort can still allow paid or side-effectful tool work to start.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body contains terminal proof from a standalone source-checkout harness showing the canceled parallel batch does not invoke the paid-style tool or afterToolCall.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body contains terminal proof from a standalone source-checkout harness showing the canceled parallel batch does not invoke the paid-style tool or afterToolCall.
Evidence reviewed

PR surface:

Source +10, Tests +77. Total +87 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 13 3 +10
Tests 1 77 0 +77
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 90 3 +87

What I checked:

  • Repository policy read: Root AGENTS.md was read fully; no scoped packages/AGENTS.md exists for the touched files, and the root policy affected the review by requiring whole-path review, current/shipped behavior checks, and real behavior proof for contributor PRs. (AGENTS.md:7, b81666ca6af2)
  • Current main has the execution gap: On current main, executeToolCallsParallel stores prepared calls as deferred thunks and later starts them with Promise.all, leaving a gap after preparation where a later hook can abort before an earlier prepared thunk executes. (packages/agent-core/src/agent-loop.ts:745, b81666ca6af2)
  • Current main lacks the guard: executePreparedToolCall on current main calls prepared.tool.execute without first checking signal?.aborted, and finalizeExecutedToolCall always reports prepared executions as started. (packages/agent-core/src/agent-loop.ts:983, b81666ca6af2)
  • Default caller path is affected: Agent defaults toolExecution to parallel and forwards beforeToolCall and afterToolCall into runAgentLoop, so awaited pre-execution hooks can hit this race on the default path. (packages/agent-core/src/agent.ts:266, b81666ca6af2)
  • PR head adds the shared abort boundary: The PR head returns an aborted result with executionStarted: false before execute, preserves executionStarted: true for real executions, and only runs afterToolCall after execution actually started. (packages/agent-core/src/agent-loop.ts:989, 231812d76644)
  • Regression test covers the race: The added test drives runAgentLoop with two parallel tool calls, aborts during the second call's beforeToolCall, and asserts neither tool executes, afterToolCall is not called, and both end events report executionStarted: false. (packages/agent-core/src/agent-loop.test.ts:1186, 231812d76644)

Likely related people:

  • steipete: GitHub path history shows repeated recent changes to packages/agent-core/src/agent-loop.ts, agent-loop.test.ts, and agent.ts, including the reusable AI runtime extraction and related agent-runtime fixes. (role: recent area contributor; confidence: high; commits: 062f88e3e3af, 5ea80c8d80fd, 56259606d15f; files: packages/agent-core/src/agent-loop.ts, packages/agent-core/src/agent-loop.test.ts, packages/agent-core/src/agent.ts)
  • vincentkoc: Recent history and PR metadata tie vincentkoc to adjacent agent-core abort work, including co-authored commits and review on the merged abort-loop PR plus recent changes to this path. (role: adjacent owner and reviewer; confidence: medium; commits: 6fcc9457020e, 7fe287b0d30d, 6765eb0166fc; files: packages/agent-core/src/agent-loop.ts, packages/agent-core/src/agent-loop.test.ts, packages/agent-core/src/types.ts)
  • szsip239: Authored the merged adjacent PR that changed the same loop/test files for abort-after-tool behavior; that fix is related but does not cover this prepared-call race. (role: adjacent abort behavior contributor; confidence: medium; commits: 25c5e393f264, 7fe287b0d30d; files: packages/agent-core/src/agent-loop.ts, packages/agent-core/src/agent-loop.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
  • 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.
Review history (3 earlier review cycles)
  • reviewed 2026-07-08T19:13:37.536Z sha 231812d :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-08T19:20:30.556Z sha 231812d :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-08T19:54:56.667Z sha 231812d :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. labels Jul 8, 2026
@harjothkhara

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Updated the PR body with source-checkout proof outside Vitest. The proof drives real runAgentLoop, aborts during the second parallel tool's beforeToolCall, and shows the already-prepared paid-style tool has execute invocations 0, afterToolCall invocations 0, and executionStarted: false for both finalized calls.

@clawsweeper

clawsweeper Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. 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 rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 8, 2026
@steipete steipete self-assigned this Jul 9, 2026
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Maintainer review and fixup complete; this is land-ready.

No known proof gaps. Release-note context: parallel tool cancellation now prevents prepared side-effectful calls from starting after a later preflight abort; CHANGELOG.md remains release-owned.

@steipete
steipete merged commit 85aa26d into openclaw:main Jul 9, 2026
83 checks passed
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 10, 2026
…#102276)

* fix(agent-core): skip prepared tools after abort

* docs(agent-core): explain parallel abort guard

* docs(changelog): note parallel cancellation fix

* docs(changelog): leave release notes release-owned

---------

Co-authored-by: Peter Steinberger <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S 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.

agent-core: parallel batch runs a prepared paid tool after the run is aborted mid-batch

2 participants