update: cutover drain can never succeed — the service is stopped before the containers it waits on
Summary
/update-nanoclaw cutover can't complete if an agent container is running. It stops the host service first, then polls for agent containers to exit. But the host is the only thing that stops idle containers, and the host's shutdown path leaves them running on purpose, so the poll can never succeed. It times out after 5 minutes and fails the update, with the service already stopped.
Agent containers stay up after a turn finishes, so this is the normal state rather than an edge case.
Mechanism
cutoverUpdate in scripts/update/transaction.ts:
state.service = runtime.detectService(state.projectRoot);
await runtime.stopService(state.service);
try {
await runtime.drainContainers(state.projectRoot);stopService in scripts/update/service.ts stops only the service wrapper (systemctl stop, launchctl bootout, or SIGTERM to the pid). It doesn't touch containers.
drainContainers in scripts/update/service.ts only observes. It never stops anything:
const listed = env.runner.tryRun(runtime, ['ps', '-q', '--filter', `label=${label}`]);
if (!listed.ok) throw new Error(...);
if (!listed.stdout) return;
if (Date.now() - started >= timeoutMs) {
throw new Error(`Timed out waiting for active NanoClaw containers: ...`);
}
await env.sleep(1_000);with timeoutMs = 300_000.
The host's SIGTERM handler in src/index.ts stops host modules, delivery polls, the sweep, the CLI server and the channel adapters, then closes the DB and exits. It doesn't stop agent containers, which looks intentional given they get adopted on the next start (Reconciled sessions at startup adopted=N in src/container-runner.ts).
So once stopService returns, nothing is left that would cause those containers to exit. The drain is waiting on something that can't happen.
Reproduction
- Send any message so an agent container spawns. It stays up after the turn completes.
- Run
/update-nanoclaw. - Cutover stops the service, blocks for about 5 minutes, then throws
Timed out waiting for active NanoClaw containers.
Observed
Host log from a real occurrence. Service stopped, about 6 minutes of nothing, then restart attempts:
[13:07:49.314] INFO Shutdown signal received signal="SIGTERM"
[13:07:49.315] INFO Channel adapter stopped channel="telegram"
[13:07:49.325] INFO Circuit breaker reset on clean shutdown
[13:13:43.355] INFO NanoClaw startingThe agent container had spawned at 13:04:25 and was still running over an hour later, so it doesn't self-exit. Earlier in the same log, the host is the thing doing the reaping:
[12:44:00.666] INFO Killing container reason="absolute-ceiling"Impact
- Any update started while an agent is idle but alive will fail.
- It fails after the service is stopped, so the install is left down.
- There's no output during the drain, so it looks like a hang rather than a wait with a known end.
Possible fixes
- Drain before stopping the service, so the host can still reap its containers. This preserves in-flight work best.
- Have
drainContainersstop the containers instead of waiting for them (docker stopon the labelled set, then confirm). - Stop labelled containers as part of
stopService.
A progress line during the wait would also help, since right now there's no way to tell it apart from a hang.
Environment
NanoClaw 2.3.0, Linux, docker, systemd-user service mode.
Source: nanocoai/nanoclaw