---
type: bug
title: Pipeline board lozenges don't update on agent state changes
---

# Bug 173: Pipeline board lozenges don't update on agent state changes

## Description

When an agent is assigned to a work item (coder, QA, mergemaster), the pipeline board lozenge should turn from amber (unassigned) to green (agent working). This works inconsistently — sometimes the lozenge goes green, sometimes it stays amber until a full page refresh. The server data is always correct (the agent IS assigned), so the issue is in the WebSocket push or frontend rendering.

## Investigation findings

**Server side is correct**: `notify_agent_state_changed()` fires on every `start_agent` call. The WS handler sends both an `AgentStateChanged` message and a `PipelineState` refresh (ws.rs:192-206). The `PipelineState` includes agent assignments via `build_active_agent_map` (workflow.rs:53-83) which correctly filters for Pending/Running agents.

**Likely race condition**: When a story advances stages (e.g. coder passes → moves to QA → QA agent assigned), two events fire close together:
1. File move (`WorkItemChanged`) → triggers `PipelineState` push (no agent yet)
2. Agent assignment (`AgentStateChanged`) → triggers `PipelineState` push (with agent)

If the first push overwrites or interferes with the second, the lozenge stays amber. This is inconsistent — sometimes the second push wins (green), sometimes the first push wins (amber).

**Frontend animation complexity**: `LozengeFlyContext.tsx` uses `useLayoutEffect` to diff previous vs current pipeline state and trigger fly-in/fly-out animations (lines 160-240). When a story changes stage AND agent simultaneously, the animation system processes both a fly-out (coder leaving) and fly-in (QA arriving), which may interfere.

**The pipeline board DOES receive `PipelineState` updates** — it's passed as a prop from `Chat.tsx:1071` (`<LozengeFlyProvider pipeline={pipeline}>`). So the data reaches the component. The issue is either:
- The second `PipelineState` (with agent) is being lost or arrives before the agent is in the HashMap
- React batches the two rapid state updates and the `useLayoutEffect` diff misses the agent change
- The fly-in/fly-out animation logic interferes when stage and agent change simultaneously

## Key files

- `server/src/http/ws.rs:188-215` — WS handler that sends both `AgentStateChanged` and `PipelineState`
- `server/src/http/workflow.rs:42-84` — `load_pipeline_state` and `build_active_agent_map`
- `server/src/agents.rs:447` — where `notify_agent_state_changed` fires in `start_agent`
- `frontend/src/components/LozengeFlyContext.tsx:160-240` — `useLayoutEffect` pipeline diff and animation logic
- `frontend/src/components/Chat.tsx:237-238` — `onPipelineState` handler that sets `pipeline` state

## Acceptance Criteria

- [ ] Agent assignment reliably turns lozenges green for all agent types (coder, QA, mergemaster)
- [ ] No full page refresh required to see agent state changes on the pipeline board
- [ ] All tests pass
