fix(btw): honour every stop signal a work run can carry - #189
Merged
YUZHEthefool merged 1 commit intoSep 14, 2026
Merged
Conversation
#157 stopped a third-party work run that `/task stop` had stopped, but it read only the event's own stop flag. `/task stop` sets that flag for a third-party runner and the agent stop request for a local one, so the local path was still uncovered: a local non-streaming run consumed every response, kept `produced` true, and ended as completed. A third-party run stopped through the agent request was uncovered too -- it kept draining its runner and ended as failed. #170 recorded a local `err` response as a failure, but `run_agent` skips the rest of a response once a stop is pending, and the marker was written after that gate. An error that arrived with a stop request was never recorded, so a failed run read as cancelled. Move the three-signal predicate next to the other work markers and give it one definition. The work loop's terminal status, its queued and pre-delivery stop checks, and both third-party handlers now read the same function, so the two stop paths cannot be handled by only one of them again. Record the `err` marker before the stop gate. Each new regression test fails on the unmodified tree: completed instead of cancelled, a fully drained runner, and a missing failure marker. Fixes #157 Fixes #170 AI-Generated: true
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
#157 made an in-flight third-party work run honour a stop, but it read only the
event's own stop flag.
/task stopsets that flag for a third-party runner andthe agent stop request for a local one, so both paths were left half-covered:
producedtrue, and ended ascompleted— the exact symptom [bug] BTW 停止第三方后台工作未中断本地执行,结束后仍标记完成 #157 set out toremove, still reachable through the other stop path.
runner and ended as
failed, notcancelled.Separately, #170 records a local
errresponse as a failure, butrun_agentskips the rest of a response once a stop is pending and the marker was written
after that gate. An error arriving together with a stop request was never
recorded, so a failed run read as
cancelled.Related issues
Fixes #157
Fixes #170
Root cause
Three signals all mean "this run was stopped", and no single one covers every
path:
event.is_stopped()— set bystop_all, the third-party path;agent_stop_requested— set byrequest_agent_stop_all, the local path;agent_user_aborted— set byrun_agentwhen it reports an abort, which alsoclears
agent_stop_requested.WorkLoop._executecollapsed these into a local expression for the terminalstatus but still tested only
is_stopped()for the queued guard and_run_detached. #173 made the third-party consumer pollevent.is_stopped, sothe agent-stop path never reached it. And in
run_agent, theerrmarker satbehind
if _should_stop_agent(astr_event): continue.Reproduction
/work <task>on a local (non-streaming) Agent, then/task stopmid-run →
/work statusreportedcompleted.drained to the last response, and the session ended
failed.errwhile the stop request is already pending → the runended
cancelledwith no failure marker.Implementation notes
btw/types.pyasstop_requested, besidethe existing work markers, and gets one definition. The work loop's terminal
status, its queued and pre-delivery stop checks, and both third-party handlers
read the same function, so the two stop paths cannot be handled by only one of
them again.
run_agentrecords theerrmarker before the stop gate. The gate stillcontrols delivery; only the bookkeeping moved.
cancelledinstead ofcompleted; a third-party run stopped via the agent request stops consumingimmediately and is
cancelledinstead offailed. No user-visible textchanges, so the
/task stopand/work statusdocs already describe this.Validation
Each of the three new regression tests was run against the unmodified tree
first, where it fails for the reported reason:
completedinstead ofcancelled, a fully drained runner, and a missing failure marker.make checkand
make qualitywere not run locally on this Windows checkout.Compatibility and risk
btw.types; no new import cycle.blocked inside one long network call is not interrupted until it returns.
Neither the local nor the remote task's cancellation is claimed beyond the
local wait.
Checklist
docs/zh/anddocs/en/when needed. (No user-visible text changed.)docs/public/openapi.json, and tests change together when routes or schemas change. (No route or schema change.)pyproject.toml,requirements.txt, anduv.locktogether. (No dependency change.)!and aBREAKING CHANGE:footer. (None; the status change is the fix.)Agent note
Goal: close the stop paths #157 and #170 left uncovered, keeping BTW off by
default and preserving authorization and request identity.
Paths touched:
astrbot/core/agent/btw/types.py,astrbot/core/agent/btw/work_loop.py,astrbot/core/astr_agent_run_util.py,astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py,and
tests/unit/test_btw_delivery.py+tests/unit/test_btw_work_loop.py.Checks run: the commands under Validation, plus each new test against the
unmodified tree. An audit of the #168–#173 stack produced the findings; a
candidate around
aclosingfailing to propagate anathrown exception to_executewas rejected after confirming_run_detachedalready records thefailure and redacts provider details.
Residual risk: stated under Compatibility and risk.
Tools used: Claude Code (Opus 5) with the repository's AGENTS.md and AI_POLICY.md.
This PR is stacked on #173 (
fix/btw-stop-third-party-work-157); merge that first.