Summary
Provider bridge workers spawned in ~/.bb/provider-maintenance-workspace accumulate and are never retired, despite PROVIDER_MAINTENANCE_IDLE_TIMEOUT_MS being 60 s. On a box that had been up ~13 h with 7–9 live threads, 7–8 of them were resident simultaneously, in cohorts up to ~12 minutes old, holding ~700–770 MB. They are npm version probes (npm view @openai/codex version, npm list @anthropic-ai/claude-code, npm list @earendil-works/pi-coding-agent), so each is a fresh ~85–136 MB node process that has no work left to do.
Verified live against bb-app 0.42.1, node v24.13.1, WSL2 (6.6.87.2), 4 installed providers (provider-acp, provider-claude-code, provider-codex, provider-pi).
This is distinct from #2308 and #2289. The thread-serving bridge workers are healthy on this build: startIdleProviderSessionReaper fires as designed, and the daemon log carries 17 Reaped idle provider sessions records with idleForMs of 1.85–2.09 M (31–35 min), matching IDLE_PROVIDER_SESSION_REAP_AFTER_MS. There is exactly one worker per live thread environment. Only the maintenance workspace accumulates.
Evidence
One snapshot, taken by reading /proc/<pid>/cwd and /proc/<pid>/cmdline (process titles are rewritten, so pgrep/ps cannot see any of this — pgrep -c node reports 0 on this box):
pid 30688 provider-acp 522s old 84 MB
pid 30689 provider-acp 522s old 85 MB
pid 30695 provider-acp 522s old 85 MB
pid 30790 provider-acp 522s old 86 MB
pid 8381 provider-claude-code 741s old 136 MB
pid 8383 provider-codex 741s old 110 MB
pid 8388 provider-acp 741s old 86 MB
All are children of the single host daemon pid, all with cwd = ~/.bb/provider-maintenance-workspace, all running bb-provider-bridge-worker.mjs.
The two cohorts are what makes this conclusive. They are 219 s apart in a single snapshot: the 741 s cohort was still alive when the 522 s cohort was spawned, and both are far past the 60 s idle timeout. Working retirement would have removed the older cohort long before the newer one existed. An earlier snapshot the same day showed 8 simultaneous workers in the same workspace at ages 370–460 s, spawned in bursts of three — consistent with mapProviderMaintenanceRequests' concurrency limit of 3.
provider-acp accumulates fastest: 5 of 8 workers in one snapshot, 4 of them in a single cohort, suggesting the ACP bridge spawns one per configured ACP agent and none is retired.
What the logs say
~/.bb/logs/host-daemon.20.log contains zero occurrences of either warning on the shutdown path:
Failed to shut down idle provider maintenance runtime — 0
Failed to shut down provider maintenance runtime during invalidation — 0
So shutdownProviderMaintenanceRuntime() is not failing loudly. Either it never runs, or it completes without terminating the worker processes.
Two candidate mechanisms
I did not narrow this further — offering both rather than asserting one:
- The idle timer is never scheduled.
withProviderMaintenanceRuntime schedules it only in its finally, and only when providerMaintenanceActiveRequests reaches 0 (apps/host-daemon/src/runtime-manager.ts, scheduleProviderMaintenanceIdleShutdown). A probe request that never settles — npm view against a slow or unreachable registry has no timeout of its own — keeps the counter above 0 forever, so the 60 s timer is never armed and the workers outlive every subsequent probe round.
- The shutdown does not reach the workers. If the timer does fire,
runtime.shutdown() resolving without terminating the spawned bb-provider-bridge-worker.mjs children would leave exactly this residue, silently, since nothing on that path logs success.
The cohort structure slightly favours (1): each round leaves its whole burst behind, rather than some workers dying and others surviving.
Suggested direction
Whatever the cause, the shape of the work looks over-provisioned for what it produces. A version probe is a short, cacheable, read-only question, and the current design pays a fresh ~100 MB node process per provider per round to ask it. Three things would each help independently:
- Cache the answer across rounds with a TTL, so an idle box stops re-probing at all.
- Bound the probe with its own timeout, so a hung
npm view cannot pin providerMaintenanceActiveRequests above 0 indefinitely.
- Log the shutdown, not only its failure. The silence on this path is why the accumulation is invisible until someone counts processes by
/proc/<pid>/exe.
Impact
On a 48 GB dev box shared with Docker and a browser this was ~700 MB of the ~10 GB the whole agent stack held, and it is the component that grows with uptime rather than with thread count — so it is the one that gets worse the longer the machine stays up. bb-app stop clears it.
Workaround
bb-app stop and restart. There is no way to retire them without taking the daemon down.
Downstream tracking issue, with the census tooling used to find this: https://github.com/PremierCrop/pcs/issues/1341
Summary
Provider bridge workers spawned in
~/.bb/provider-maintenance-workspaceaccumulate and are never retired, despitePROVIDER_MAINTENANCE_IDLE_TIMEOUT_MSbeing 60 s. On a box that had been up ~13 h with 7–9 live threads, 7–8 of them were resident simultaneously, in cohorts up to ~12 minutes old, holding ~700–770 MB. They arenpmversion probes (npm view @openai/codex version,npm list @anthropic-ai/claude-code,npm list @earendil-works/pi-coding-agent), so each is a fresh ~85–136 MB node process that has no work left to do.Verified live against bb-app 0.42.1, node v24.13.1, WSL2 (6.6.87.2), 4 installed providers (
provider-acp,provider-claude-code,provider-codex,provider-pi).This is distinct from #2308 and #2289. The thread-serving bridge workers are healthy on this build:
startIdleProviderSessionReaperfires as designed, and the daemon log carries 17Reaped idle provider sessionsrecords withidleForMsof 1.85–2.09 M (31–35 min), matchingIDLE_PROVIDER_SESSION_REAP_AFTER_MS. There is exactly one worker per live thread environment. Only the maintenance workspace accumulates.Evidence
One snapshot, taken by reading
/proc/<pid>/cwdand/proc/<pid>/cmdline(process titles are rewritten, sopgrep/pscannot see any of this —pgrep -c nodereports0on this box):All are children of the single host daemon pid, all with
cwd = ~/.bb/provider-maintenance-workspace, all runningbb-provider-bridge-worker.mjs.The two cohorts are what makes this conclusive. They are 219 s apart in a single snapshot: the 741 s cohort was still alive when the 522 s cohort was spawned, and both are far past the 60 s idle timeout. Working retirement would have removed the older cohort long before the newer one existed. An earlier snapshot the same day showed 8 simultaneous workers in the same workspace at ages 370–460 s, spawned in bursts of three — consistent with
mapProviderMaintenanceRequests' concurrency limit of 3.provider-acpaccumulates fastest: 5 of 8 workers in one snapshot, 4 of them in a single cohort, suggesting the ACP bridge spawns one per configured ACP agent and none is retired.What the logs say
~/.bb/logs/host-daemon.20.logcontains zero occurrences of either warning on the shutdown path:Failed to shut down idle provider maintenance runtime— 0Failed to shut down provider maintenance runtime during invalidation— 0So
shutdownProviderMaintenanceRuntime()is not failing loudly. Either it never runs, or it completes without terminating the worker processes.Two candidate mechanisms
I did not narrow this further — offering both rather than asserting one:
withProviderMaintenanceRuntimeschedules it only in itsfinally, and only whenproviderMaintenanceActiveRequestsreaches 0 (apps/host-daemon/src/runtime-manager.ts,scheduleProviderMaintenanceIdleShutdown). A probe request that never settles —npm viewagainst a slow or unreachable registry has no timeout of its own — keeps the counter above 0 forever, so the 60 s timer is never armed and the workers outlive every subsequent probe round.runtime.shutdown()resolving without terminating the spawnedbb-provider-bridge-worker.mjschildren would leave exactly this residue, silently, since nothing on that path logs success.The cohort structure slightly favours (1): each round leaves its whole burst behind, rather than some workers dying and others surviving.
Suggested direction
Whatever the cause, the shape of the work looks over-provisioned for what it produces. A version probe is a short, cacheable, read-only question, and the current design pays a fresh ~100 MB node process per provider per round to ask it. Three things would each help independently:
npm viewcannot pinproviderMaintenanceActiveRequestsabove 0 indefinitely./proc/<pid>/exe.Impact
On a 48 GB dev box shared with Docker and a browser this was ~700 MB of the ~10 GB the whole agent stack held, and it is the component that grows with uptime rather than with thread count — so it is the one that gets worse the longer the machine stays up.
bb-app stopclears it.Workaround
bb-app stopand restart. There is no way to retire them without taking the daemon down.Downstream tracking issue, with the census tooling used to find this: https://github.com/PremierCrop/pcs/issues/1341