# fix\(server\): task_status reports how long a running delegated task has been silent · PR \#23898 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/23898)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: open

Source repository: Unavailable fork

Source branch: fix/delegated-task-idle-signal

Target branch: main

Head commit: 50acc3565a7462fb943ec9cb13863496330f8075

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/23898)

[Commits](https://git.cafe/pingdotgg/t3code/pull/23898/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/23898/diffs?format=markdown)

## Description

---

## Problem

A delegated Codex child can sit in **Working** with no provider events at all. Neither the parent agent (`task_status`) nor the user can tell it apart from a turn that is doing work. This is the silent post-tool case in #17503. Below is the notification sequence @juliusmarminge asked for, from the last tool result through the end of each silence.

**Versions:** T3 Code Nightly `0.1.1-nightly.20261020.3411` (macOS), `codex-cli 0.163.0` over `codex app-server`, model `gpt-6.1-sol` via a Responses-compatible CLIProxyAPI on loopback. Codex stream settings are defaults.

**Silence 1: a hung request after a tool result (15m35s, never answered)**

| UTC | Dir | Method | Key fields |
|---|---|---|---|
| 21:39:28.953 | in | `item/started` | commandExecution |
| 21:39:28.960 | in | `item/completed` | commandExecution `failed`, exitCode 1 (the command's real exit) |
| 21:39:29.008 | in | `thread/tokenUsage/updated` | last notification |
| *21:39:29 – 21:55:04* | | **silence** | no `thread/status/changed`, `error`, `willRetry`, deltas, or `turn/completed`. The Codex rollout is also empty. |
| 21:55:04.228 | out | `turn/interrupt` | manual restart by the parent agent |
| 21:55:04.376 | in | `thread/status/changed` / `turn/completed` | `idle` / `interrupted`, `error: null` |

**Silence 2: the same thread after the restart (5m13s before first output, then normal)**

| UTC | Dir | Method | Key fields |
|---|---|---|---|
| 21:55:04.767 | in | `thread/status/changed` / `turn/started` | `active` |
| 21:55:04.782 | in | `item/completed` | userMessage |
| *21:55:04 – 22:00:18* | | **silence** | the same: no status change, error, retry, or reasoning |
| 22:00:18.085 | in | `item/started` | agentMessage (first model output) |

The proxy logged `499 | 15m35s` for the first request when the client closed it, and `200 | 5m15s` for the second. It logged no retries. These are slow or hung upstream requests that never return response headers, not a 429 retry sequence. Codex's stream idle timeout never fires, so it emits nothing and T3 has no signal to act on. A 5-minute silence can also be healthy, as the second one was, so this PR reports idleness and does not time anything out.

## Change

- **`task_status`** (contract, server, tool description, docs) now returns three fields for running tasks:
  - `lastActivityAt`: the last recorded change to the child thread.
  - `idleForMs`: time since then.
  - `hasRunningItem`: whether an open item belongs to the run's active attempt. Items left open by an attempt that a restart superseded don't count.

  A long `idleForMs` with `workState: working`, `hasRunningItem: false` and no pending request points to a stalled provider. The parent can then restart or cancel the task. All three are null/false for queued and finished tasks.
- **Web Working row:** after 3 minutes without item activity, with nothing in progress and no pending request, the row adds a quiet "· No activity for N" hint. A pure helper in `TimelineWorkGroupRows.logic.ts` derives this from data the client already has.
- **Not included:**
  - **Timeouts and auto-fail.**
  - **Adapter changes.**
  - **Mobile.** Its working indicator is separate and would be a follow-up.
  - **Streaming items with no output.** An open text or reasoning item still counts as in progress, because reasoning can legitimately be silent. Codex's stream idle timeout covers a hang in the middle of a stream.

## Scope and approval

This addresses #17503, specifically its "no no-progress signal on an active turn" gap. The reporter also asked for that state to be "visible … to parent agents through `task_status` for delegated tasks". The PR stays informational (no new timeouts or retries) so that it fits the problem the issue already establishes. It does not address the issue's `systemError` recovery or 429 backoff parts.

## Before / after

`MessagesTimeline` rendered on its own with a running turn whose last activity was 15 minutes ago. Upstream ignores the idle input, and the two "before" captures are byte-identical.

| Before (upstream) | After |
| --- | --- |
| ![](https://github.com/omar7550/t3code/releases/download/idle-signal-evidence-20261022/working-row-before-idle-15m.png) | ![](https://github.com/omar7550/t3code/releases/download/idle-signal-evidence-20261022/working-row-after-idle-15m.png) |

Fixture-driven component captures, not a live stuck provider. The desktop and mobile shells were not exercised. [Full sanitized notification sequence](https://github.com/omar7550/t3code/releases/download/idle-signal-evidence-20261022/notification-sequence.md).

## Verification

- `OrchestratorMcpService.activity.test.ts` (using `TestClock`) covers:
  - `idleForMs` growing as time advances;
  - open items from a superseded attempt vs. the active attempt;
  - an open item with no provider turn (workspace preparation);
  - completed items and items from inactive runs;
  - finished tasks returning nulls.
- `TimelineWorkGroupRows.logic.test.ts` covers:
  - the 3-minute threshold;
  - suppression while an item is in progress;
  - the restart baseline;
  - ignoring items from other runs;
  - missing attempt or start returning null;
  - formatting.
- The new tests fail on upstream, where the fields and helper don't exist.
- `vp test run` on the MCP service, activity and toolkit integration tests (44 passed) and the web logic tests (6 passed).
- `vp run check:changed upstream/main` passed: format, lint, knip, typecheck for 29 packages, and 191 tests.
- Three independent review rounds, none blocking. Their wording, query-cost, item-filter, render-flash and test-gap findings were fixed, including tests that fail when the lazy-loading guard is removed.
- Not checked: a live stalled provider end to end, Electron, mobile.

Done by Claude Opus 5.5 in Claude Code, running in T3 Code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

