Repository navigation
Stop unstarted tool calls after batch cancellation #17
Description
Problem
When a model response contains multiple tool calls, aborting the first call does not stop the remaining calls in that response. A following write can still execute even though the final loop result is aborted.
This was reproduced offline using the actual core runLoop, actual CLI ToolExecutor, and a scripted model response on 0.3.1. It was not a live-model attempt to induce that exact sequence. The affected code is unchanged at main commit 01b49ad5a4d3e60395aa61f094275042a88aae0c.
Deterministic reproduction
Return both calls in one ScriptedTransport response:
[
{"name":"bash","arguments":{"command":"sleep 5"}},
{"name":"write","arguments":{"path":"batch-write-after-esc.txt","content":"batch continued after abort"}}
]Use a disposable working directory and an AbortController. Abort approximately 100 ms after the first tool_start. With automatic execution, the recorded result was:
{"reason":"aborted","toolStarts":["bash","write"],"fileExists":true,"requests":1}Separately, passing an already-aborted signal directly to ToolExecutor.run(write, signal) still created a file. Interactive approval may insert a new approval prompt, so the write observation above specifically uses automatic execution, not a claim that a fresh approval was bypassed.
Starting points
loop.ts: tool-call batch has no abort check between calls; the next check is at the next model turn.ToolExecutor.run()has no initial abort guard, andwriteperforms its side effect without consulting the signal.
Minimal scope and acceptance criteria
- Once cancellation is observed, no remaining tool in the response starts, and no next model request is made.
- Direct executor calls with an already-aborted signal refuse side effects before hooks, approval prompts, or command/file execution.
- End with
abortedand preserve coherent transcript/checkpoint state for the completed, interrupted, and unstarted tool calls. Do not mark an unstarted call as executed. - Add deterministic regressions for a two-call batch, an already-aborted executor, and a normal uninterrupted batch. Exercise both event order and the absence of a marker file.
- Preserve normal completion and existing resume protections; do not weaken uncertain-operation handling to make the test pass.
No model API is required: use ScriptedTransport, a disposable fixture, and bounded cleanup. A focused core/executor patch is sufficient; a general scheduler rewrite is outside scope.
This differs from the terminal-cancellation issue (an existing process remains alive) and from #7 (checkpoint classification of an uncertain write).
Priority: P1. Not a good first issue: cancellation, tool batching, and durable transcript semantics intersect here.
Related work and coordination
Related process-lifetime fix: #16. Preserve the uncertain-operation resume protection tracked in #7.
Activity
TaewoooPark commentedon Oct 4, 2026
Resolved by #40. All five acceptance criteria have been independently verified and are now checked.
The real core loop and CLI executor were exercised offline with a running
bashfollowed bywritein one scripted model response. After cancellation, the remaining write has no start/end execution events or durable mutation intent, receives an explicit not-executed observation, and causes no next model request. The completed transcript matches the checkpoint; resume retains it, and uncertain-operation protections remain intact. Cancellation after the final call also returnsabortedbefore repair or loop detection.For historical accuracy, #37 had already added the direct-executor cancellation guard and prevented the original file-write side effect on current main. #40 preserves that guard and fixes the remaining batch-loop execution records and termination behavior. The already-aborted direct call was independently checked to refuse hooks, approval, and file changes.
The four submitted regressions and nine additional independent scenarios passed. Current main plus the exact PR patch passed all 1,643 local tests, and all five CI jobs passed: https://github.com/TaewoooPark/Motifcode/actions/runs/37100770804 . Validation used scripted responses and disposable fixtures; no live Infron/Motif model call was made.
Thanks @adity982 for the focused contribution. This records the source fix in main; an npm release is separate.
Merged commit: 54f9d5b . Post-merge main CI also passed all five jobs: https://github.com/TaewoooPark/Motifcode/actions/runs/37180040334 .