Skip to content

[Bug]: Cancelling a background-only delegated task reports completed with an interim summary #16739

Description

@coygeek

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Summary

Cancelling a nonterminal T3-owned delegated task after its provider turn has ended, while native background work is still pending, stops that work but leaves task_status reporting completed with the child's interim waiting message as its result. A parent reading the task later cannot distinguish this stopped task from successful completion through the task result fields.

Steps to reproduce

These are the supplied live probe steps, not a fresh local provider run. Use the measured nightly under Runtime or environment. The important prerequisite is a completed provider run with pending native background work; this case is not an already-terminal task with a published result.

  1. In an active parent thread, call delegate_task with the following input. Save the returned taskId.
{
  "target": {"providerInstanceId": "claudeAgent", "model": "claude-haiku-4-5"},
  "role": "test",
  "mode": "wait",
  "timeoutMs": 5000,
  "task": "Cancellation probe. Run the shell command `sleep 300` and then reply with the single word DONE. Do not do anything else and do not change any files."
}
  1. In the supplied case, the Claude runtime refused the foreground command. The child started sleep 300 as a native background shell task, posted an interim waiting message, and ended its provider turn. Confirm this prerequisite before proceeding. The wait call returned status: "running" and waitTimedOut: true.
  2. While the background command is still running, call task_cancel for the saved task ID with a reason and a fresh clientRequestId. The observed response was status: "cancel_requested".
  3. Read task_status immediately and again after the background work stops. Check whether the task result identifies cancellation and whether the requested DONE was ever produced. Check the background process separately to distinguish a stopped command from its natural completion.

The missing fresh verification is a current-build provider run that reaches this exact background-only state. The recorded Claude Agent SDK and Claude Code versions are unknown, so the prompt alone is not established as deterministic across installations. No new live delegated probe was started during this investigation.

Expected behavior

The task result should preserve that cancellation ended still-pending task work, rather than presenting the interim message as successful completion. The documented task contract says that a completed turn with live nested work is not a completed task, that summary is the final task result, and that the task remains nonterminal until its known work finishes. Returning an existing status when cancelling an already-terminal task is a separate documented case; here task_cancel returned cancel_requested while the task was nonterminal.

This report does not require a particular representation or a rewrite of an already-published successful result. It asks for an observable task outcome that distinguishes this cancellation from finished work.

Actual behavior

The supplied live record reports the following sequence. The background task ID in the quoted message is redacted; no task or thread identities are needed for reproduction.

After the wait timeout:
  task_status: status=running, workState=working, waitTimedOut=false
After task_cancel returned cancel_requested:
  task_status: status=running, workState=waiting_for_children
  latestTerminalStatus=completed, summary=null
  child timeline: run_interrupt_request on the already-completed first run
About one minute later:
  task_status: status=completed, workState=result_available
  hasPendingChildRuns=false, resultContextTransferId=null
  summary and latestTerminalSummary:
    "The background sleep command (ID: [REDACTED]) is now running. Waiting for it to complete..."
  no DONE reply and no cancelled/interrupted run outcome
Thirty minutes later:
  same task result; child still has only the first completed run

The recorded process check found no sleep 300 about 94 seconds after it started, well before its natural 300-second completion. The saved evidence therefore supports that the work stopped early, although this investigation did not repeat that process observation. No completion wake reached the parent, consistent with disposal of automatic parent delivery.

Static inspection of the current default branch found the same relevant implementation as the measured release. readTask derives terminal status from the completed result run once pending work clears, and subagentResultForRun takes that run's latest assistant message. thread.stop selects a completed run with pending background work and requests an interrupt. The background settlement path only calls settleInterruptedRun for a still-running run, and that helper itself rejects a completed run. No cancellation outcome is added to the task result by cancelTask.

Evidence

  • Expected source: apps/server/src/mcp/toolkits/orchestrator/tools.ts:79 describes nonterminal task work and final summaries; docs/orchestration-v2/orchestrator-mcp-server.md:269-289 describes task_status and task_cancel. The terminal-result preservation test in apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts:1876-1912 covers an already-published successful result followed by a later active turn, rather than this nonterminal background-only case.
  • Failure source: Supplied saved live probe, items 9-13, summarized inline under Actual behavior; corroborating current source is apps/server/src/mcp/OrchestratorMcpService.ts:1244-1269,1920-2067, apps/server/src/orchestration-v2/SubagentProjection.ts:158-254, and apps/server/src/orchestration-v2/Orchestrator.ts:8177-8210,8526-8620,9014-9088,9557-9670.
  • Evidence provenance: supplied
  • Local verification: not-run
  • Reproduction completeness: incomplete
  • Missing fact: A fresh current-build provider reproduction of the completed-turn/pending-native-background-work state, including the Claude Agent SDK and Claude Code versions.

The primary failure evidence is a saved live probe, not a recollection. This investigation independently read the saved probe and traced the current source from task_cancel through thread.stop, the provider interrupt effect, background settlement, delegatedTaskProgress, readTask, and task finalization. Runtime reproduction and project tests were not run here. The relevant files have no diff between the measured release and the inspected default branch/latest published nightly.

The source can be inspected at the pinned default-branch revision. apps/server/src/orchestration-v2/EffectWorker.ts:170-196 dispatches background settlement after the provider interrupt. DelegatedCompletionDelivery.test.ts:941-1005 also distinguishes a background-only child's interim reply from its result during restart recovery; it does not define this cancellation outcome.

Restoration check

Using a child whose only run has completed while its native background command remains pending, cancel the still-nonterminal task and wait for settlement. Passing behavior stops the background work and exposes a task outcome that distinguishes cancellation from successful completion; it does not publish the interim waiting message as a successful result. Later task_status reads should preserve that distinction. As a control, cancelling a task that had already published a successful final result may retain its existing result while stopping later child-thread work, as the existing integration test specifies.

Impact

Minor bug or occasional failure

Version or commit

The supplied live observation used T3 Code Nightly 0.0.46-nightly.20261006.2752, whose published release target is d8d037eae1b77a77f372fd1afd2662a31ebe37c8. The latest published release, including prereleases, at the final check was v0.0.46-nightly.20261007.2761; its target is the inspected main revision pinned under Evidence. The latest stable release is v0.0.45; no claim of reproduction on that stable release is made.

Environment

The recorded environment was macOS, Darwin 27.0.0, Apple silicon, desktop app. Parent provider/model: claudeAgent / claude-opus-5-5, full-access / default. Child provider/model: claudeAgent / claude-haiku-4-5. The embedded Claude Agent SDK and Claude Code versions were not measured.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: One saved probe shows a stopped delegated task reported as successfully completed with an interim result. The affected result is consequential for parents that recover or consume task state later. Frequency and reach across providers are unknown; no lost files or wider incident are claimed.
  • Workaround status: unverified
  • Workaround basis: A parent could remember its own cancellation request separately, but no durable workaround was tested for a later parent turn or restart that reads only the task result.

Activity

  1. juliusmarminge commented on Oct 7, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Thanks for the detailed write-up. I traced this on current main (10f39eb) and the code paths behave the way you describe:

    • task_cancel (apps/server/src/mcp/OrchestratorMcpService.ts:2035-2068): if the task status is already terminal, it returns that status. Otherwise it needs workState === "waiting_for_children" or a latestActiveRun(child), calls stopWithinLimit, drops the completion delivery, and returns cancel_requested. Nothing is written to the task that would make it read as cancelled later.
    • settleInterruptedRun (apps/server/src/orchestration-v2/Orchestrator.ts:8190-8225) does nothing unless run.status === "running" and the attempt is still running. The thread.background-work.settle path (Orchestrator.ts:~8578-8592) also calls it only when stoppedRun.status === "running". Once the provider turn has ended and the run is completed, stopping its background work can't turn the run into interrupted/cancelled.
    • Summary: subagentResultForRun (apps/server/src/orchestration-v2/SubagentProjection.ts:158-200) takes the newest non-empty assistant message or turn item for the run. For a completed run whose last message was an interim "waiting on background work" note, that note becomes the result (OrchestratorMcpService.ts:430, 1267, 1303).
    • Background tracking: CLAUDE_OPAQUE_BACKGROUND_TASK_KINDS (apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts:1786-1810) only maps local_bash onto the Waiting roster as command/monitor. It sets what the pending-background roster shows, not the run's status.

    Confirmed from code: cancel records no cancelled marker, the interrupted settlement only applies to running runs, and the summary is the run's last assistant text. Inferred, not reproduced here: that the task sits in a cancel-pending state and then reads as completed/result_available about a minute later with no parent wake. That fits the paths above (the completion delivery is dropped on cancel), but I haven't run it myself.

    A likely fix direction: record a cancel intent on the task/run when task_cancel stops background work on a turn-ended run, and settle it as cancelled/interrupted (with a parent wake) instead of reusing the completed run's interim message.

    Related, not duplicates: #15888, #15082, #16110, #13490. In the same area, merged #16002 and open #15201 don't address this case.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions