Problem
WP_Agent_Workflow_Request_Controller::cancel() can report and deliver a terminal cancelled operation without proving that the workflow runner accepted cancellation.
Two races share the same missing ownership contract:
- If cancellation arrives before
WP_Agent_Workflow_Runner::start_run() creates the run-control row, WP_Agent_Run_Control::request_cancel() returns no accepted state. The controller ignores that result, records the workflow result as cancelled, and delivers terminal cancellation, while the worker may subsequently start and execute normally.
- If an active worker has already passed its final cancellation check, the controller does not fence cancellation against the worker lease. It can deliver
cancelled while the worker subsequently persists succeeded or failed, leaving controller and recorder state inconsistent.
Reproduction
Cancel before run start
- Reserve a request-controller operation before its runner creates the workflow run-control row.
- Call
cancel() for the operation.
- Observe a terminal cancelled response even though
request_cancel() did not persist cancellation.
- Start the worker and observe normal execution.
Cancel after the worker's last check
- Advance an operation until the worker holds the operation lease and passes its final run-control cancellation check.
- Call
cancel() concurrently.
- Observe cancelled terminal delivery.
- Allow the worker to finish and observe a conflicting terminal recorder result.
Impact
- External consumers receive a false terminal cancellation.
- Workflow side effects can occur after cancellation was reported complete.
- Recorder and request-controller terminal states can disagree.
- Cleanup can unschedule actions while the active worker still owns execution.
Acceptance criteria
- Cancellation has a durable operation-level intent that exists before workflow run creation and is consumed when the runner starts.
- An active execution lease is fenced so cancellation and worker terminalization have one authoritative winner.
cancel() reports accepted/terminal state only when durable cancellation is proven.
- Controller, recorder, and run-control state cannot terminalize to conflicting outcomes.
- Deterministic concurrent tests cover cancellation before run creation and after the worker's final cancellation check.
AI assistance
OpenAI GPT-5.6 Sol via OpenCode general coding and review subagents traced the request-controller and runner cancellation paths, identified the two races, searched existing trackers, and drafted this report. Chris Huber directed the orchestration and remains responsible for the issue and resulting implementation.
Problem
WP_Agent_Workflow_Request_Controller::cancel()can report and deliver a terminalcancelledoperation without proving that the workflow runner accepted cancellation.Two races share the same missing ownership contract:
WP_Agent_Workflow_Runner::start_run()creates the run-control row,WP_Agent_Run_Control::request_cancel()returns no accepted state. The controller ignores that result, records the workflow result as cancelled, and delivers terminal cancellation, while the worker may subsequently start and execute normally.cancelledwhile the worker subsequently persistssucceededorfailed, leaving controller and recorder state inconsistent.Reproduction
Cancel before run start
cancel()for the operation.request_cancel()did not persist cancellation.Cancel after the worker's last check
cancel()concurrently.Impact
Acceptance criteria
cancel()reports accepted/terminal state only when durable cancellation is proven.AI assistance
OpenAI GPT-5.6 Sol via OpenCode general coding and review subagents traced the request-controller and runner cancellation paths, identified the two races, searched existing trackers, and drafted this report. Chris Huber directed the orchestration and remains responsible for the issue and resulting implementation.