Incus daemon wedged on the drill host — forensics before reprovisioning #26
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:cli
scope:drill
scope:host
scope:installer
scope:templates
scope:tiers
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/box#26
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The drill host stopped answering Incus. Run 6 stalled inside
setup-host.sh, andteardown-host.shthen stalled too — right after they, before printing anything, which places it at its first command:incus list "user.claudebox=1"in a command substitution. Both stalls are the same symptom:incusdis not answering.Do not reprovision the host yet. A fresh server would come up green and prove nothing. If claudebox (or the drill) can wedge Incus — by force-deleting VMs, by leaving orphaned
qemuprocesses, by filling the storage pool with 60GiB boxes — that is a claudebox finding, and it lives exactly one Ctrl-C away from a user's real workflow (claudebox rm, an interruptednew). Reprovisioning erases it.This issue is the forensic sequence. Capture before you fix — a daemon restart destroys the evidence.
Step 1 — evidence (restart nothing)
Run all of it, paste the output as a comment on this issue.
Step 2 — read the evidence
df -h /at or near 100%, or the storage pool fullbin/claudebox), and the drill mints up to 4 boxes. The drill should shrink the disk for its own boxes, or the docs must state the host requirement. Worth its own issue.qemuprocesses with no live instanceclaudebox rm --force/ an interruptednewcan orphan a VM. This is reachable from normal use.incusdin D state, or D-state processes under/var/lib/incusincus --version) and the trace; not ours to fix, but ours to avoid.Step 3 — recovery, least destructive first
Try each, re-testing
timeout 10 incus list; echo $?after every one. Stop at the first that works and record which.Step 4 — once it answers again
Re-run the drill on this host, not a new one:
It now clears leftover boxes and reverts the phase-D mutations before
setup-host.sh(#25), and every step is bounded and narrated — so if it stalls again, it will say where instead of going quiet.Reprovision only if step 3 cannot revive the daemon. In that case, capture step 1's output here first — that output is the finding.
Why this is worth the detour
The audit (#15) has one probe left (A3, sibling isolation) and six runs have failed to fire it — every time for a different reason, mostly drill plumbing (see
drill/RUNS.md). It would be easy to reach for a clean host and call it done. But a tool whose boxes can wedge the host's container daemon is a tool with a bug, and the only place that bug is visible is on the host that has it right now.Evidence is in, and it rules out the disk
Not disk — 16G used of 452G. Not a dead daemon —
incusd(pid 22114) isSsl, holding/var/lib/incus/unix.socket, 48 tasks. But its API is deadlocked:incus list→ exit 124 (timeout),incus info→ 143,incus operation list→ 124.The cause is visible in the process table:
-S= start paused, CPU halted. Incus always launches qemu with-Sand then resumes it via the monitor socket. Both VMs have sat paused for ~30 minutes: Incus started them and never resumed them. The daemon is stuck mid-operation on the two boxes an aborted run left behind.The timeline nails it:
incusdhas been up 1h38m, but both qemu processes are only ~30 min old — they were (re)started exactly when run 6 began. Run 6 stalled insetup-host.sh, and setup-host runs:— rewriting a profile that two running VMs are attached to, which forces a live CPU/memory reconfiguration on both. That is where it deadlocked.
The finding
setup-host.shcan wedge the Incus daemon when boxes are running. It presents itself as idempotent and safe to re-run, and it is — only on an idle host. Re-running it with live boxes (an upgrade, a re-provision, a second drill run) is reachable from normal use, and the failure mode is a hung daemon, not an error message.Two things follow:
setup-host) is the right mitigation, not just a drill nicety.setup-host.shitself should not silently reconfigure a profile that running instances depend on — it should detect them and refuse, or warn and skip the profile edit. Tracking that as its own fix.Memory is worth a footnote:
incus.serviceshows 4G resident, 8.7G peak on a 15GiB host with two 3GiB VMs. Not obviously the cause, but not nothing.Next: capture the deadlock, then recover
incusdis Go, soSIGQUITdumps every goroutine stack and exits — which both gives us the trace and clears the wedge. Capture before killing:Then re-run the drill on this host (#25 clears leftovers before setup-host, so the trigger is gone):
Recovery — run this block (the previous one stalled at
systemctl restart)Two things went sideways and neither is a problem:
systemctl restarthung. systemd asks the daemon to stop politely, and the daemon is deadlocked — so it waits out its stop timeout. Don't ask politely.incusdruns with--logfile=/var/log/incus/incus.log, soSIGQUIT's stack dump went there (hencewc -l= 16 on the journal capture).The qemu kills did land (
Killed, twice). Ctrl-C whatever is still hanging, then:Paste the
goroutineoutput here if there is any. If there isn't, no loss — the evidence is already sufficient (see below).Then: re-run the drill on THIS host
#25 deletes leftover boxes before
setup-host.shruns, so the trigger is gone. This should finally answer A3 and close #15.If the daemon will not come back
Only then reprovision — and say so here first. The forensics above are the finding; they are not lost by rebuilding the host afterwards.
The finding, restated (this stands with or without the trace)
setup-host.shcan deadlock the Incus daemon when it is re-run while boxes are running.Evidence:
archive,peer) stuck in qemu's-Sstate — started, never resumed — for ~30 minutes. Incus always launches qemu paused and resumes it over the monitor socket; these never got resumed.incusdhad been up 1h38m — i.e. they were (re)started exactly when run 6 began.setup-host.sh, which unconditionally runsincus profile edit claude-dev < profiles/claude-dev.yaml— rewriting a profile two live VMs are attached to, forcing a CPU/memory reconfiguration on running guests.list/info/operation listall time out; socket still held; disk fine (16G/452G).setup-host.shadvertises itself as idempotent and safe to re-run. It is — on an idle host. Re-running it with live boxes is reachable from ordinary use (an upgrade, a re-provision, a second run), and the failure mode is a hung daemon rather than an error. It should detect running instances and refuse, or skip the profile edit. Filing that as its own issue.Daemon recovered — and it was stuck shutting down, not deadlocked
pgrepshowed onlyincusd shutdown(systemd'sExecStophelper) waiting on a main daemon we had alreadySIGKILLed and that could never answer it. The unit sat inactivatingforever, so everyincuscall blocked on a socket activation that could not complete. Thesql: database is closedlines were post-mortem noise from the kill, not the cause.What cleared it:
→
active,EXIT=0. No reprovision needed. (No goroutine dump survived —grep -c goroutine /var/log/incus/incus.log→ 0 — so the exact mutex is unnamed, but the sequence in the previous comment stands on the process evidence.)The recovered
incus listexposed TWO findings the drill could never have reportedFinding 1 — the NIC inside a VM is
enp5s0, noteth0. This is why A3 has never fired.The profile names the device
eth0(profiles/claude-dev.yaml), but inside a VM guest predictable naming renames itenp5s0. Every address lookup in the drill has been hunting an interface that does not exist:/\(eth0\)$/→ never matchedip -4 -o addr show dev eth0→ also never matchedSix runs, two "fixes", and the assumption underneath both was wrong. A3's failure was never a network finding — it was this. (Note
box_ipv4()inbin/claudeboxhas the sameeth0assumption baked into its comment, though it takes the first address rather than selecting by name.)Finding 2 — a clone gets the SAME IP as its source. This is a claudebox bug, not a drill bug.
peerwas cloned fromarchive/authed, and both boxes now hold10.87.0.128. The snapshot→clone workflow — log in once, snapshot, clone forever — is the entire reuse story of this tool, and clones are colliding on the network.Almost certainly: the guest's
/etc/machine-idis cloned with the disk, DHCP uses it as the client identifier, and dnsmasq therefore hands the clone its source's lease. (Consistent with an earlier run where the two boxes did differ — leases collide only when both are up and the client-id matches.)It also means A3 was doomed even with the interface name right: probing
archive → 10.87.0.128is archive probing itself.Confirmation — one line, please
Reading it:
machine-id→ identical DHCP client-id → same lease. Fix is a machine-id reset at clone time (cloud-init, orclaudebox new --from).incus copydid not regenerate the MAC — different fix, same symptom.What this changes
enp5s0in VMs,eth0in containers — read whichever exists rather than assuming).Closing: the forensic sequence this issue demanded ran to a verdict (see thread) — the daemon was not deadlocked but stuck in shutdown, an
incusd shutdownExecStop helper waiting forever on a main process we had already killed, wedging socket activation. Cleared withpkill -9 -f 'incusd shutdown'+ stop/reset-failed/start; the recovery recipe now lives indoctor.sh's daemon pre-check, which refuses to run and prints it when the daemon is unresponsive.Runs 7–11 all completed on this host since. The related debris hypothesis is also settled: the drill no longer poisons the host (#32) and the doctor exists precisely to prove that before every run.
🤖 Generated with Claude Code