pingdotgg / pingdotgg/t3code

[Bug]: Workflow between phases reports zero working agents — banner, panel footer and badge all read as finished

Open
#8,720 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

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

packages/client-runtime, apps/web

Steps to reproduce
  1. Run a Workflow with at least two phases, e.g. phase('Find') with 3 agents, then phase('Verify') with 3 agents.
  2. Wait for every phase-1 member to settle, before the first phase-2 member spawns.
  3. Read the three aggregate liveness surfaces during that gap: the composer background-work banner, the Agents panel footer, and the agents right-panel badge.

Unit repro without a provider: call deriveAgentPanelModel with a roster of one kind: "workflow" coordinator at status: "running" plus three members carrying parentAgentId, all at a settled status. It returns liveCount: 0.

Expected behavior

A workflow that is between phases is still running. The coordinator is executing the script body — collecting the previous phase's results, filtering, deciding what to fan out next — and will spawn more agents. The aggregate surfaces should keep saying so, ideally with the phase position (Workflow · phase 2 of 4) rather than an agent count that happens to be zero at that instant.

Actual behavior

Every aggregate count drops to zero in the gap, so the run reads as finished:

  • The composer banner degrades from "3 agents working" to the generic "Background work" (apps/web/src/components/ChatView.tsx:4812).
  • The Agents panel footer's "● N working" chip disappears entirely — it is gated on runningCount + waitingCount > 0 (apps/web/src/components/AgentsPanel.tsx:571) — leaving only "3 settled".
  • The agents right-panel badge count goes to 0 (apps/web/src/components/ChatView.tsx:6874).

Then phase 2 spawns and everything comes back. On a workflow with several phases the UI cycles through looking-finished once per phase boundary.

The per-phase rows themselves are fine: AgentsPanel.tsx:360 renders {phase.activeCount} active · {phase.settledCount} done and a done checkmark per phase, and AgentPanelWorkflowGroup.phases carries the structure. The phase model is there — only the rollup that everything else reads is wrong.

Root cause: packages/client-runtime/src/state/subagentRuntime.ts:833

if (agent.kind === "workflow" && (members.get(agent.id) ?? []).length > 0) continue;

The coordinator is skipped from every status bucket whenever it has at least one member. Between phases it has members — all of them settled — so it is skipped, and liveCount = runningCount + waitingCount is 0 even though the coordinator's own status is running.

This is a regression from the fix for #5807. That issue was real: the coordinator was being counted as an extra working agent. But note what the superseded PR #5808 said about its own version of the fix:

A memberless coordinator still counts, so a workflow that has not spawned its first member — or is between phases — does not read as zero agents.

The PR that actually merged, #6672 (2026-08-15), hoisted a single continue to the top of the tally loop as a "strict superset" of #5808. It is a superset for the overcount, but it drops the between-phases case #5808 was guarding: the guard keys on members.length > 0, and between phases the member list is non-empty, just entirely settled.

Side effect of the same continue: the bucket-sum invariant idleCount + runningCount + waitingCount + settledCount === roster.length that #5808 preserved is now short by the number of workflow coordinators that have members.

Proposed fix

Sketch.

Distinguish "coordinator is a container for work happening in its members" from "coordinator is the only thing still running". Skip the coordinator only when at least one member is itself live:

const workflowMembers = members.get(agent.id) ?? [];
const hasLiveMember = workflowMembers.some(
  (m) => m.status === "running" || m.status === "pending" || m.status === "waiting",
);
if (agent.kind === "workflow" && hasLiveMember) continue;

That keeps #5807 fixed — while members are working the coordinator is not double-counted — and makes a between-phases workflow count as exactly one live unit instead of zero. Token aggregation should keep its existing members.length === 0 rule, since that guards double-counting rather than liveness.

Better still, expose the phase position the panel already computes so the banner can read Workflow · phase 2 of 4 instead of an agent count. A count is the wrong primitive for a workflow: it is legitimately zero at every phase boundary.

Impact

Misleading UI — the user reads a run as finished and walks away, or interrupts it. The banner is also the only visible Stop affordance for background work (per #5807), so its accuracy is what people act on.

Related
  • #5807 / #6672 — the overcount fix this regressed out of
  • #5808 — superseded PR whose guard preserved the between-phases case
  • #4962 — mobile analog, still open: thread looks idle while background subagents work
  • #5518, #5476 — completion notifications and auto-settle firing while background work continues
Version or commit

main @ 1f8ed54ad

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at packages/client-runtime/src/state/subagentRuntime.ts:833 and reproduce the issue with deriveAgentPanelModel using a running workflow coordinator and settled members. Then inspect the aggregate consumers at apps/web/src/components/ChatView.tsx:4812 and :6874 and AgentsPanel.tsx:571. Done means a between-phase workflow remains visibly active across all three surfaces without double-counting live members, while the bucket-sum invariant is preserved.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.