readinessReconcile wedges about five minutes after live start, and dispatch never resumes. This is a different fault from the registration outage fixed in #343 — that one is genuinely closed.
Evidence
Two authenticated /evidence reads of the live container, 15 minutes apart:
| field |
20:36 read |
20:51 read |
heartbeat.updatedAt |
20:36:12.764Z |
20:51:28.069Z |
readinessReconcile.state |
healthy |
stalled |
readinessReconcile.lastStartedAtMs |
1787516980583 |
1787516980583 (unchanged) |
readinessReconcile.lastCompletedAtMs |
1787516920577 |
1787516920577 (unchanged) |
readinessReconcile.consecutiveFailures |
0 |
0 |
Decoded:
- last completed cycle:
20:28:40Z
- last started cycle:
20:29:40Z
- heartbeat still updating at:
20:51:28Z
The daemon started at 20:24:05Z, completed exactly one reconcile cycle at 20:28:40Z (lastDurationMs: 1424), started the next at 20:29:40Z, and has not finished it in 22 minutes.
consecutiveFailures: 0 is the important part: it is not erroring and retrying, it is blocked inside a call. A failure counter cannot see a hang, and the heartbeat timer keeps ticking straight through it — so every surface except lastCompletedAtMs reports healthy.
Why this matters beyond the immediate stall
The rest of the system looks perfect: fleetControlPlane: closed, eventListener: subscribed, fleet agent factory-cloud-b712dab5 online in presence, phase: running. Those are all real — the registration fix worked. But dispatch is still dead, and only one field in the entire evidence payload says so.
Canary #350 was filed at 20:38Z matching the deployed gate exactly (requireLabel: factory, requireTitlePrefix: [factory], factory routed). Twelve-plus reconcile intervals later it has zero comments and no label change. That is consistent with the reconcile loop never running again.
Deliverable
Find the unbounded call inside the readinessReconcile cycle and bound it.
The likely shape is an await on a fleet/presence/roster/relayfile read with no per-call timeout. Note that a deadline checked between awaits does not bound a call that never returns — the bound has to be on the call itself.
Required:
- Per-call timeouts inside the reconcile cycle, not a loop-level deadline.
- A stall must be loud. A cycle that exceeds its budget should fail, increment a counter, and surface a reason —
stalled with consecutiveFailures: 0 and no lastError gives an operator nothing to act on. Whatever it was waiting on should be named.
- Self-healing: the loop must start the next cycle rather than staying wedged until reboot. Restart-on-failure never fires for a hang, which is why this survived 22 minutes.
- Test: a reconcile whose dependency never resolves ⇒ the cycle aborts, the counter increments, and the next cycle still starts. Plus a control proving the test would notice if the abort were removed.
Diagnosis first
Before changing anything, establish which call is hanging. A fix aimed at the wrong await will look correct and change nothing. lastDurationMs: 1424 for the one cycle that did complete is a useful baseline — normal is ~1.4s, so whatever is blocking is pathological, not slow.
Also worth checking as a possible input: agent presence currently returns 2281 agents (~261KB). If reconcile reads full presence per cycle, payload growth is a plausible trigger for a call that used to return.
readinessReconcilewedges about five minutes after live start, and dispatch never resumes. This is a different fault from the registration outage fixed in #343 — that one is genuinely closed.Evidence
Two authenticated
/evidencereads of the live container, 15 minutes apart:heartbeat.updatedAt20:36:12.764Z20:51:28.069ZreadinessReconcile.statehealthystalledreadinessReconcile.lastStartedAtMs17875169805831787516980583(unchanged)readinessReconcile.lastCompletedAtMs17875169205771787516920577(unchanged)readinessReconcile.consecutiveFailuresDecoded:
20:28:40Z20:29:40Z20:51:28ZThe daemon started at
20:24:05Z, completed exactly one reconcile cycle at20:28:40Z(lastDurationMs: 1424), started the next at20:29:40Z, and has not finished it in 22 minutes.consecutiveFailures: 0is the important part: it is not erroring and retrying, it is blocked inside a call. A failure counter cannot see a hang, and the heartbeat timer keeps ticking straight through it — so every surface exceptlastCompletedAtMsreports healthy.Why this matters beyond the immediate stall
The rest of the system looks perfect:
fleetControlPlane: closed,eventListener: subscribed, fleet agentfactory-cloud-b712dab5online in presence,phase: running. Those are all real — the registration fix worked. But dispatch is still dead, and only one field in the entire evidence payload says so.Canary #350 was filed at
20:38Zmatching the deployed gate exactly (requireLabel: factory,requireTitlePrefix: [factory],factoryrouted). Twelve-plus reconcile intervals later it has zero comments and no label change. That is consistent with the reconcile loop never running again.Deliverable
Find the unbounded call inside the
readinessReconcilecycle and bound it.The likely shape is an
awaiton a fleet/presence/roster/relayfile read with no per-call timeout. Note that a deadline checked between awaits does not bound a call that never returns — the bound has to be on the call itself.Required:
stalledwithconsecutiveFailures: 0and nolastErrorgives an operator nothing to act on. Whatever it was waiting on should be named.Diagnosis first
Before changing anything, establish which call is hanging. A fix aimed at the wrong await will look correct and change nothing.
lastDurationMs: 1424for the one cycle that did complete is a useful baseline — normal is ~1.4s, so whatever is blocking is pathological, not slow.Also worth checking as a possible input:
agent presencecurrently returns 2281 agents (~261KB). If reconcile reads full presence per cycle, payload growth is a plausible trigger for a call that used to return.