You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I searched existing issues and did not find a duplicate.
I included enough detail to reproduce or investigate the problem.
Area
apps/server, apps/web, packages/client-runtime
Summary
Subagent status in the Agents panel is unreliable in two ways:
After a usage limit, a subagent goes red and stays red even after it recovers and finishes the work.
While a subagent runs, its status never updates — the current step is set at spawn and not touched again until the run ends.
Both come from data the app already receives and then drops.
Note on existing issues
The session-level half of this is already known: #6513, with PRs #7165, #5077 and #5473 in flight (and #6639 closed). None of them touch subagent status — they all work at the thread/session layer, and none change subagentRuntime.ts or the Agents panel. Part 1 below is the subagent-level gap those fixes leave open. #7128 is the opposite direction (progress wrongly reviving an idle task), so please read part 2 alongside it.
Part 1 — a subagent that recovers from a usage limit stays red
Steps to reproduce
Spawn several subagents on a Claude session.
Let the account hit its 5-hour usage limit mid-run.
Wait for the limit window to reset and the subagents to be re-dispatched.
Watch the Agents panel.
Actual behavior
The subagents are marked failed, and they stay red for the rest of the session — including while they are working again and after they complete.
From my own logs (~/.t3/userdata/logs/provider/events.<threadId>.log), four subagents died on one limit and all four came back:
09:38:56.716 account.rate-limits.updated {"status":"rejected","rateLimitType":"five_hour","resetsAt":1786969200}
09:38:56.718 task.updated taskId=aa7ad7db11045589e status=failed
error="Agent terminated early due to an API error: You've hit your session limit · resets 2:20pm"
09:49:23.309 task.started taskId=aa7ad7db11045589e <- same taskId, working again
10:00:45.545 task.completed status=completed <- finished; row still red
Expected behavior
A usage limit is a pause, not a failure. The row should show something like "waiting · resets 2:20pm", and it must go back to normal once the agent resumes.
Why it happens
Three things line up:
The rate-limit signal is discarded.ClaudeAdapter.ts:3474 and CodexAdapter.ts:1401 both emit account.rate-limits.updated carrying the window type and resetsAt. Grepping the tree for that string returns only the two emitters and the contract — ProviderRuntimeIngestion.ts has no case arm, so it falls through. The UI can't know a limit happened. (PR fix(server): type the account rate-limit runtime payload #5473 makes the same observation.)
It collapses into failed. With no rate-limit check in the LLM path, the result hits the default return "failed" at ClaudeAdapter.ts:956. There's also no better value available: the status union at subagentRuntime.ts:22 has no throttled/waiting-for-quota member.
failed is frozen, and re-dispatch reuses the taskId.subagentRuntime.ts:515 blocks a progress tick from reopening a terminal agent:
Only an explicit running/pending status event reopens the row, so the recovered agent inherits the old terminal state under the same id.
Blast radius: one stuck member turns the whole collapsed workflow dot red (AgentsPanel.tsx:482) and the chat CTA dot too (MessagesTimeline.tsx:2171).
For contrast, the codebase already models this properly for the GitHub CLI — packages/contracts/src/vcs.ts:76 has a first-class "rate-limited" kind. The LLM path never got it.
Part 2 — a running subagent gets no status updates
Actual behavior
Every canonical event for one subagent across a 7-minute run:
Across all my logs: 1309 progress notifications, 65 status updates. The progress events are projected into token usage only — description and last_tool_name are dropped.
Expected behavior
The panel reflects the agent's most recent real activity. The data needed for this is already arriving many times per minute.
Also contributing
Progress rows are collapse-on-write.ProviderRuntimeIngestion.ts:592 writes under a stable id task-progress:{threadId}:{taskId}, so one row exists per task ever. If an event is missed, the last value is pinned with nothing to notice it's old.
No liveness timer anywhere, client or server. So a finished agent reads "Working" with a happily ticking elapsed clock (AgentsPanel.tsx:101 updates textContent only and never re-folds).
Terminal events aren't synthesized on teardown.stopSessionInternal (ClaudeAdapter.ts:3595) doesn't drain liveTaskIds, so an agent that ends without a task_notification shows running forever.
Part 3 — small related bug: retryable Codex errors paint the red pill
The lastError spread sits outside the willRetry ternary, so a retryable error leaves the session running with lastError set. The sidebar red pill keys purely off lastError being non-null (Sidebar.tsx:337), and ProviderRuntimeIngestion.ts:1592 only clears lastError when status === "ready" — turn.started maps to "running", so resuming never clears it.
Claude's equivalent path does the right thing: ClaudeAdapter.ts:3325 turns api_retry into session.state.changed{state:"running"} to keep the session visibly alive.
Suggestions
Consume account.rate-limits.updated into a non-terminal throttled state at the subagent level, not just the thread banner the open PRs add.
Let a live progress tick reopen a failed agent (subagentRuntime.ts:515) so red can clear itself. Please check this against [Bug]: Finished threads stay Working after delayed task progress #7128 — that issue wants progress to not revive an idle task, so the two need a shared rule.
Project task_progress into status, not only token usage. The current step is already on the wire ~18x/minute.
Add a lastActivityAt so a quiet agent looks different from a busy one.
Items 1 and 3 need no new plumbing — only reading events the app already emits.
Impact
Moderate: the Agents panel can't be trusted, so I wait on work that's already done, or kill agents that were fine.
Version or commit
0.0.34-nightly.20260814.1092
Environment
macOS (Darwin 25.5.0) desktop app, Claude and Codex providers.
How I traced this
The nightly ships sourcemaps with full sourcesContent, so all line numbers above refer to real source, not minified offsets. You may want to check whether that's intended for released builds.
Before submitting
Area
apps/server,apps/web,packages/client-runtimeSummary
Subagent status in the Agents panel is unreliable in two ways:
Both come from data the app already receives and then drops.
Note on existing issues
The session-level half of this is already known: #6513, with PRs #7165, #5077 and #5473 in flight (and #6639 closed). None of them touch subagent status — they all work at the thread/session layer, and none change
subagentRuntime.tsor the Agents panel. Part 1 below is the subagent-level gap those fixes leave open. #7128 is the opposite direction (progress wrongly reviving an idle task), so please read part 2 alongside it.Part 1 — a subagent that recovers from a usage limit stays red
Steps to reproduce
Actual behavior
The subagents are marked
failed, and they stay red for the rest of the session — including while they are working again and after they complete.From my own logs (
~/.t3/userdata/logs/provider/events.<threadId>.log), four subagents died on one limit and all four came back:Expected behavior
A usage limit is a pause, not a failure. The row should show something like "waiting · resets 2:20pm", and it must go back to normal once the agent resumes.
Why it happens
Three things line up:
The rate-limit signal is discarded.
ClaudeAdapter.ts:3474andCodexAdapter.ts:1401both emitaccount.rate-limits.updatedcarrying the window type andresetsAt. Grepping the tree for that string returns only the two emitters and the contract —ProviderRuntimeIngestion.tshas no case arm, so it falls through. The UI can't know a limit happened. (PR fix(server): type the account rate-limit runtime payload #5473 makes the same observation.)It collapses into
failed. With no rate-limit check in the LLM path, the result hits the defaultreturn "failed"atClaudeAdapter.ts:956. There's also no better value available: the status union atsubagentRuntime.ts:22has no throttled/waiting-for-quota member.failedis frozen, and re-dispatch reuses the taskId.subagentRuntime.ts:515blocks a progress tick from reopening a terminal agent:Only an explicit
running/pendingstatus event reopens the row, so the recovered agent inherits the old terminal state under the same id.Blast radius: one stuck member turns the whole collapsed workflow dot red (
AgentsPanel.tsx:482) and the chat CTA dot too (MessagesTimeline.tsx:2171).For contrast, the codebase already models this properly for the GitHub CLI —
packages/contracts/src/vcs.ts:76has a first-class"rate-limited"kind. The LLM path never got it.Part 2 — a running subagent gets no status updates
Actual behavior
Every canonical event for one subagent across a 7-minute run:
Status is set at spawn, then nothing for 7m19s. A later run in the same log has a 30m12s gap.
In that same 7-minute window the SDK sent 128
task_progressnotifications, each carrying the current step:{"task_id":"a76927b010c77a27d","description":"Running Inspect worktree layout", "last_tool_name":"Bash","usage":{"tool_uses":1,"duration_ms":3419}}Across all my logs: 1309 progress notifications, 65 status updates. The progress events are projected into token usage only —
descriptionandlast_tool_nameare dropped.Expected behavior
The panel reflects the agent's most recent real activity. The data needed for this is already arriving many times per minute.
Also contributing
ProviderRuntimeIngestion.ts:592writes under a stable idtask-progress:{threadId}:{taskId}, so one row exists per task ever. If an event is missed, the last value is pinned with nothing to notice it's old.AgentsPanel.tsx:101updatestextContentonly and never re-folds).stopSessionInternal(ClaudeAdapter.ts:3595) doesn't drainliveTaskIds, so an agent that ends without atask_notificationshows running forever.Part 3 — small related bug: retryable Codex errors paint the red pill
CodexSessionRuntime.ts:1428:The
lastErrorspread sits outside thewillRetryternary, so a retryable error leaves the sessionrunningwithlastErrorset. The sidebar red pill keys purely offlastErrorbeing non-null (Sidebar.tsx:337), andProviderRuntimeIngestion.ts:1592only clearslastErrorwhenstatus === "ready"—turn.startedmaps to"running", so resuming never clears it.Claude's equivalent path does the right thing:
ClaudeAdapter.ts:3325turnsapi_retryintosession.state.changed{state:"running"}to keep the session visibly alive.Suggestions
account.rate-limits.updatedinto a non-terminal throttled state at the subagent level, not just the thread banner the open PRs add.failedagent (subagentRuntime.ts:515) so red can clear itself. Please check this against [Bug]: Finished threads stay Working after delayed task progress #7128 — that issue wants progress to not revive an idle task, so the two need a shared rule.task_progressinto status, not only token usage. The current step is already on the wire ~18x/minute.lastActivityAtso a quiet agent looks different from a busy one.Items 1 and 3 need no new plumbing — only reading events the app already emits.
Impact
Moderate: the Agents panel can't be trusted, so I wait on work that's already done, or kill agents that were fine.
Version or commit
0.0.34-nightly.20260814.1092Environment
macOS (Darwin 25.5.0) desktop app, Claude and Codex providers.
How I traced this
The nightly ships sourcemaps with full
sourcesContent, so all line numbers above refer to real source, not minified offsets. You may want to check whether that's intended for released builds.