[Bug]: Workflow between phases reports zero working agents — banner, panel footer and badge all read as finished
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
- Run a
Workflowwith at least two phases, e.g.phase('Find')with 3 agents, thenphase('Verify')with 3 agents. - Wait for every phase-1 member to settle, before the first phase-2 member spawns.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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