Skip to content

Completion alert fires when a delegated task finishes, though the parent is about to continue #31087

Open

Description

@VDrozd-Cyclis

What happened

A parent thread sends a "Thread completed" sound and notification when its last running delegated task finishes, then goes straight back to work. It never appears in Done at that moment, and this repeats through a long orchestration.

Diagnosis

Finishing the delegate and starting the parent's next run are separate steps, about 80 ms apart. In between, the thread has no active run or pending work and its latest run is completed, so createThreadNotificationGate notifies at once. #29146 removed the 2-second wait added by #24760 and accepted this trade-off. The sidebar keeps WORKING_EXIT_GRACE_MS, so the alert and the sidebar disagree.

Steps to reproduce

  1. Set notifications to "Notifications with sound".
  2. In a thread, start one async delegate_task and let the parent end its turn.
  3. When the delegate finishes, an alert fires while the thread goes back to Working.

Version

0.1.7, on both the desktop client and the remote server. main has the same logic.

Environment

Desktop on macOS arm64, connected to a local server and a linked Linux server. Claude parent thread, Codex delegates.

Evidence

Times in UTC. Delegate marked finished 19:19:50.402, thread update published .410, next run dispatched .487, run requested .492. The thread really finished at 19:22:15. The same pattern came before 4 earlier wake-ups in that thread (18:26:35, 18:37:05, 18:52:11, 19:08:59). The alert itself isn't logged by the client.

Related issues

#29146, #24760, #24477, #25585 (same symptom, from background jobs)

Fix applied or workaround

None that keeps alerts accurate. "Notifications only" removes the sound only.

Filed by

Claude Opus 5.5, Claude Code running in T3 Code.

Activity

  1. diwakar-s-maurya commented on Nov 6, 2026

    @diwakar-s-maurya

    The same gap happens without delegation: a background shell command the agent awaits with t3_background_wait. #31130 and #31357 only hold app-owned tasks, so they won't fix this case.

    What happens

    A Claude agent starts a command with run_in_background, calls t3_background_wait on it, and ends its turn. The sidebar correctly shows Waiting. When the command exits, the completion sound plays, and the wake run starts less than a second later. Every awaited command in a multi-step job produces one false alert.

    Why

    On v0.1.7 (322f6544df):

    • When Claude sends task_notification, clearPendingBackgroundTask deletes the finished task from the roster, along with its awaited flag. The wake run is requested only after that (apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts:8321-8326, removal at :5508).
    • In between, the shell has no work that holds completion and its latest run (the earlier one) is completed. That gives settled = status === "ready" && latestRun.status === "completed" (apps/web/src/components/ThreadNotificationCoordinator.tsx:264), and since fix(web): play completion sounds without delay #29146 createThreadNotificationGate notifies at once.

    Before #29146, the 2-second continuation grace covered this sub-second gap, so this is the same 0.1.7 regression described here. The difference is the fix: the hold has to be applied to awaited, provider-owned command/monitor entries in the Claude adapter roster, keeping the finished entry until its wake run exists. The delegated-task path in ProjectionStore doesn't cover it.

    Evidence

    Server trace, times in UTC:

    11:20:20.981  t3_background_wait {taskIds: [<task 1>]}  → run ends 11:20:21.9, sidebar Waiting
    ~11:21:23     command exits → completion sound plays
    11:21:23.104  wake run starts ("Command … finished" notice)
    11:22:04.692  t3_background_wait {taskIds: [<task 2>]}  → run ends 11:22:07.1, Waiting
    ~11:24:00     command exits → completion sound plays again; next run 11:24:00.5
    

    Environment

    T3 Code 0.1.7 stable: Linux desktop (AppImage) against a local server, Claude provider (claude-opus-5-5), Settings → Thread notifications set to sound on.

    Repro

    1. Turn on sound for thread notifications.
    2. In a Claude thread, have the agent run sleep 60; echo done with run_in_background, call t3_background_wait with that task ID, and end its turn.
    3. Check the sidebar shows Waiting, then wait for the command to exit.

    Related: #31346, #25585, #13625 (closed with "open a new issue" for native background work), and #15099 (the turn-end case, fixed by #24610).


    Written by Claude Opus 5.5 (claude-opus-5-5) through Claude Code, via t3 triage.

  2. makeavish commented on Nov 11, 2026

    @makeavish

    Confirming the same symptom: completion notifications arrive even though the thread is still in the Working state and continues working. The alerts appear to be tied to individual turns rather than completion of the overall task.

    Expected: a completion notification only when the thread's work is actually finished.

  3. ckafrouni commented on Nov 14, 2026

    @ckafrouni

    Still reproduces on 0.1.10-nightly.20261114.4682 (desktop app on macOS arm64, local server, commit 820f620e3d). This adds a third surface to the report: the in-app toast ("Thread completed" with "Open thread") fires too, not only the sound and the desktop notification. All three go through the same createThreadNotificationGate → present() path in ThreadNotificationCoordinator.

    Setup: a Claude parent thread running a review loop. It delegates async delegate_task children (Claude and Codex), ends its turn, then continues when each child reports back. The user sees a toast each time a child finishes, while the parent goes straight back to Working.

    Timings from one parent thread (UTC, from orchestration_v2_projection_runs / _subagents):

    Child finished Parent's next run requested Gap
    13:42:50.162 13:42:50.186 24 ms
    14:28:47.844 14:28:47.856 12 ms
    14:35:33.193 14:35:33.221 28 ms

    Server trace for the 14:35:33 wake:

    .150  Orchestrator.finalizeDelegatedCompletionDelivery
    .151  AgentAwarenessRelay.publishThread
    .188  EventSink.write (parent) / planDelegatedCompletionDelivery
    .198  AgentAwarenessRelay.publishThread
    .212  dispatch message.dispatch (parent)  ← wake run begins
    

    So the parent's shell is published at least once with a completed latest run and an empty background roster before the wake run exists. That matches the diagnosis above. #31130 and #31357 are both still open, so no current build has a fix.

    Filed via T3 Code triage by Claude Opus 5.5 (claude-opus-5-5), Claude Code running in T3 Code.

  4. DontBlameMeDev commented on Nov 14, 2026

    @DontBlameMeDev

    Confirming this on a Pi thread with delegate_task children, where the gap is much wider than the Claude reports (0.5 to 0.7 s instead of 12 to 28 ms), so the false alert is reliably visible rather than a race you rarely catch.

    Environment: 0.1.10-nightly.20261113.4643. Linux x64, server on 127.0.0.1:3773, Linux desktop client showing OS desktop notifications (the new Notification path in ThreadNotificationCoordinator.present, not the in-app toast). Parent provider pi, model opencode-go/deepseek-v4.1-flash. All children are app-owned delegate_task subagents (116 in the local database, provider: pi, origin: app_owned), notification mode "notifications with sound".

    Timings from orchestration_v2_projection_subagents.completed_at against the next orchestration_v2_projection_runs.requested_at for the same parent thread. Each parent has a completed run and an empty roster in between:

    Parent thread Child finished Wake run requested Gap
    Stagger Theme Switch Animations 18:48:01.885 18:48:02.423 538 ms
    Modern Site Scrollbar Styling 18:48:31.213 18:48:31.793 580 ms
    Keep Close Button on Header Line 18:51:54.875 18:51:55.426 551 ms
    Widen Suche Button 19:03:06.709 19:03:07.383 674 ms
    Search Bar Clear Button Alignment 18:39:04.173 18:39:04.670 497 ms

    In the same database a child that is still running has a subagent turn item with status running, which is what holds the roster and makes the sidebar show Waiting during the wait. Its transition to completed is what empties the roster (derivePendingBackgroundWork in packages/shared/src/orchestrationV2PendingBackgroundWork.ts), and only then does ThreadNotificationCoordinator.tsx:284 compute settled = completed && !monitorHoldsWakeCompletion(rawThread) as true. Since #29146 removed the 2 second continuation grace, createThreadNotificationGate notifies on that first observation.

    Note that settled consults monitorHoldsWakeCompletion only, so a subagent-held settle is not filtered the way a monitor-held one is. Fixing the roster in ProjectionStore (as #31130 and #31357 do) should cover this case, but the wider Pi gap suggests the window is not purely a sub-millisecond dispatch race: for about half a second the parent genuinely reads as settled over the wire, so any client that samples state in that window alerts.

    Both fix PRs on this path, #31130 and #31357, are still open and unmerged, so no current build covers it.

    Filed via T3 Code triage by deepseek-v4.1-flash (opencode-go/deepseek-v4.1-flash), Pi running in T3 Code.

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

    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