Skip to content

Background task notifications (task_notification / task_started) are silently dropped causing desynced conversations #336

@theahura

Description

@theahura

Note: writeup written by AI, but I reviewed it deeply to ensure it aligned with what we're running into. I'm less confident on the 'root cause' section -- I did not dig as deeply into the actual implementation of the ACP adapter -- but I am leaving it here in case the pointer is correct.

Summary

When Claude Code launches background tasks via the Task tool with run_in_background=true, the ACP adapter silently drops the SDK's task_notification and task_started system messages. This causes two problems:

  1. During a turn: Completion notifications that arrive while prompt() is active are consumed from the generator but discarded, so the ACP client never learns about them. This is less problematic, but could be an issue for consumers that want to know about these messages. (Note: I as a dev care about this less)
  2. Between turns: The generator is not consumed between prompt() calls, so notifications accumulate internally in the SDK. They only surface on the next prompt() call, one turn at a time, requiring the user to send N-1 extra messages to drain N completions. (Note: I as a dev care about this far more)

Reproduction

  1. Start a session via ACP
  2. Ask the agent to launch 3 background tasks (e.g., "Spin up 3 background agents using Task with run_in_background. Agent A sleeps 5s, B sleeps 10s, C sleeps 15s.")
  3. Wait for all tasks to complete (>15s)
  4. Observe: only 1 completion appears in the client
  5. Send any message → a second completion appears
  6. Send another → the third appears
  7. From this point, all future messages are 'out of sync' because the agent will always be responding to a message from ~3 messages ago, while the user sees the most recent response.

Root Cause

In acp-agent.ts, the prompt() method's message processing loop handles SDKMessageTemp from the Query async generator. Under the case "system" branch, task_notification and task_started subtypes fall through to a no-op:

// acp-agent.ts, prompt() method
case "system":
  switch (message.subtype) {
    case "init":
      break;
    case "compact_boundary":
    case "hook_started":
    case "task_notification":   // <-- silently dropped
    case "hook_progress":
    case "hook_response":
    case "status":
    case "files_persisted":
    case "task_started":        // <-- silently dropped
      // Todo: process via status api
      break;
  }

These messages carry background task lifecycle events that could be forwarded to the ACP client via this.client.sessionUpdate().

Proposed Fix

Part 1 — Within-turn delivery (straightforward)

Handle task_notification and task_started in the switch statement by converting them to ACP SessionNotifications and calling this.client.sessionUpdate(), similar to how stream_event and assistant message types are already handled.

Part 2 — Between-turn delivery (needs design)

After prompt() returns, the Query generator is no longer being consumed. System messages (including task completions) accumulate in the SDK's internal buffer with no way to reach the ACP client.

One approach: run a background drain loop between turns that continues calling .next() on the generator and forwards system messages via sessionUpdate(), stopping when the next prompt() call begins. This doesn't trigger LLM work — it's purely reading from the SDK's local event buffer.

The correctness concern is ensuring the drain loop doesn't accidentally consume messages belonging to the next prompt() turn. This depends on how the SDK sequences user input pushes vs generator yields.

Impact

Any ACP client using background agents (e.g., the Task tool with run_in_background) will experience:

  • Missing real-time notifications for background task completion
  • Messaging that falls out of sync, requiring extra user messages to drain buffered completions
  • Stale status information (client thinks tasks are still running when they've finished)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions