Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EPZ4GJ8L5Jqf8UiPwbn1kt
14 KiB
SPIKE — 2nd external drive "drops" the 1st (multi-drive mutual exclusion / flapping)
- Date: 2026-06-29
- Class: Diagnostic / read-only. No fix. No deliberate reproduce was needed — the symptom is
continuously live in the controller gate logs, so the host-vs-guest
mountinfodiff was read directly from the running state (an attach would only re-stop apps, adding no evidence). - Host: felhom-pve (
demo-felhom, 192.168.0.162); guest LXC 9201 (init pid 2137736). - Live versions: agent 0.43.0 (NON-ROOT
felhom-agentsince 2026-06-28), controller 0.89.0. - Verdict: Detection bug (matrix row 3), NOT a propagation fault and NOT a controller-gate
bug. The guest demonstrably keeps both drive submounts; the agent mis-reports
bound_under_parent=falsefor every drive because the non-root agent cannot resolve the guest init PID (lxc-infois denied / unusable under thefelhom-agentuser). The controller gate is the messenger — it correctly stops apps for a drive its report says is absent. Root cause is the 2026-06-28 root→non-root agent migration (same regression family as the leaf-pin + PBS-key issues):lxc-infowas never added to the agent sudoers allowlist.
1. The question (§3) — answered
Does the guest actually lose drive A's submount (propagation fault), or does the guest keep it while the agent mis-reports
BoundUnderParent=false(detection bug)?
The guest keeps BOTH submounts; the agent mis-reports. Propagation is intact (the guest's parent
bind is master:1, a live slave of the host's shared:1 parent, and both drives are visible in the
guest namespace right now). The fault is purely in the agent's guest-sees detection.
2. Raw evidence
A1/A2 — host parent + submounts (both drives bound, propagation shared)
findmnt /mnt/felhom-drives:
/mnt/felhom-drives /dev/mapper/pve-root[/mnt/felhom-drives] shared
├─/mnt/felhom-drives/felhom-flash /dev/sdc1[/felhom-data] shared
└─/mnt/felhom-drives/felhom-usb /dev/sdb1[/felhom-data] shared
/proc/self/mountinfo (host):
137 33 252:1 /mnt/felhom-drives /mnt/felhom-drives shared:1
99 137 8:33 /felhom-data /mnt/felhom-drives/felhom-flash shared:51 ← child of parent 137
100 33 8:33 /felhom-data /mnt/felhom-drives/felhom-flash shared:51 ← ALSO child of root 33 (doubling)
109 137 8:17 /felhom-data /mnt/felhom-drives/felhom-usb shared:49
110 33 8:17 /felhom-data /mnt/felhom-drives/felhom-usb shared:49
A3 — GUEST view (the BoundUnderParent truth) — both drives present
guest_pid=2137736
571 324 252:1 /mnt/felhom-drives /mnt/felhom-drives shared:482 master:1 ← slave of host group 1 (propagation LIVE)
108 571 8:33 /felhom-data /mnt/felhom-drives/felhom-flash shared:195 master:51
344 571 8:17 /felhom-data /mnt/felhom-drives/felhom-usb shared:210 master:49
(+ duplicate entries 572/111/345 via the guest's second parent path — see §4 doubling)
The guest sees both felhom-flash and felhom-usb as mountpoints under the parent.
Agent /disks report — both reported absent (the contradiction)
felhom-flash guest_path=/mnt/felhom-drives/felhom-flash bound_under_parent=False guest_attached=False
felhom-usb guest_path=/mnt/felhom-drives/felhom-usb bound_under_parent=False guest_attached=False
Guest mountinfo says present; agent says bound_under_parent=False. The report is wrong.
The cause — lxc-info fails under the non-root agent
# as root (what the agent did PRE-migration): works
lxc-info -n 9201 -p -H → 2137736
# as the felhom-agent user, directly: FAILS (no usable $HOME)
sudo -u felhom-agent lxc-info -n 9201 -p -H
→ lxc-info: Permission denied - Failed to create directory "/home/felhom-agent/"
# as the felhom-agent user, via sudo (the runner's path): DENIED (not in NOPASSWD allowlist)
sudo -u felhom-agent sudo -n lxc-info -n 9201 -p -H
→ sudo: a password is required
# NB: the non-root user CAN read /proc/<pid>/mountinfo (exit 0, sees both drives) —
# the ONLY broken step is resolving the guest PID via lxc-info.
Controller gate — the live symptom (every cycle, both drives)
[gate] drive ABSENT /mnt/felhom-drives/felhom-usb — stopped+blocked 1 app(s): [nextcloud]
[gate] drive ABSENT /mnt/felhom-drives/felhom-flash — stopped+blocked 0 app(s): []
[stacks] Stopping stack: nextcloud (repeats ~every minute)
3. Causal chain (code-verified)
boundUnderParent()(felhom-agent/internal/localapi/disks.go:703) →guestAttach.GuestSeesMount(ctx, vmid, stablePath).GuestSeesMount(intermediary.go:236) callsguestInitPID→b.runner.Run(ctx, "lxc-info", "-n", vmid, "-p", "-H")(intermediary.go:256).- The agent runs non-root with
privileged.mode: sudo; the runner shells every command viasudo -n.lxc-infois absent fromconfigs/felhom-agent.sudoers(line 93 grants onlyFELHOM_MOUNT/DISK/PROVISION/FORMAT/DNSMASQ/GUESTHOOK/INTERMEDIARY— none listslxc-info), sosudo -n lxc-info→ "a password is required" → error. (Run un-sudo'd it also fails: thefelhom-agentuser has no writable$HOMEfor lxc-info's lock dir.) guestInitPIDreturns""→GuestSeesMountreturnsfalse(pid empty) for every drive →bound_under_parent=falsefor all.- Controller
planDriveGates(felhom-controller/.../web/intermediary.go:202) setspresent[guestPath] = … || d.BoundUnderParent→false; the gate (line 224)!present && !Disconnected → Stop apps + SetDisconnected(true). Every external drive is judged ABSENT and its apps stopped. The gate is correct given its input — do NOT "fix" it (the task's own warning: it is a real data-availability signal, here fed a false reading).
Why it looks like "attaching B drops A" (mutual exclusion): with bound_under_parent permanently
false, the gate marks both drives ABSENT on every cycle. An attach/enroll transiently re-binds and
clears the just-touched drive's disconnected flag (so it briefly shows Aktív), while the gate
continues to stop the other drive's apps — and re-stops the first on the next cycle. The net effect is
perpetual flapping (nextcloud stopped ~every minute); the user perceives the most-recently-attached
drive as "the active one" and the other as having "dropped." It is not single-active by design — it is
both-absent by mis-detection.
4. Secondary observation (latent, NOT the cause) — host-side "doubling"
Each drive submount appears twice on the host: once under the parent (mount 137) and once under
root (mount 33), because the parent /mnt/felhom-drives carries shared:1 — the same peer group as
/ (root id 33 is also shared:1). It was never isolated into its own group. Cause:
EnsureSharedParent calls mount --make-private (intermediary.go:110), but make-private is NOT
in the sudoers (line 85 allows only mount --make-shared /mnt/felhom-drives, not --make-private),
so the agent's isolation step is denied. (The root-run boot script felhom-shared-parent.sh does run
make-private, but the live parent is currently in root's group regardless — worth confirming at the
next host boot.) This doubling makes AttachDrive always see countHostMounts==2 and take the
umount-all→rebind normalize branch, and propagates binds/umounts into root's namespace. It does not
cause the reported symptom (the guest still sees both drives), but it is a real correctness smell from
the same non-root-allowlist gap and should be fixed alongside.
5. Interpretation matrix — matched row
| Observation | Root cause |
|---|---|
Host keeps both submounts, guest keeps both, but agent reports BoundUnderParent=false |
Detection bug in GuestSeesMount/boundUnderParent — specifically PID resolution: lxc-info denied/unusable under the non-root agent (sudoers gap + homeless user) → guestInitPID="" |
Rows ruled out: no propagation churn (parent is a stable shared:1 mountpoint, guest is master:1,
peer groups did not change); guest parent is not private (it is a live slave); host did not
drop any submount.
6. Remedy options for the operator (NOT executed)
- Grant
lxc-infoin the agent sudoers (minimal, restores pre-migration behavior). Add a fixed vector e.g./usr/bin/lxc-info -n [0-9]* -p -Hto aCmnd_Aliasand the NOPASSWD line,visudo -cf, reload. Verifiedsudo lxc-info -n 9201 -p -Hworks as root → returns2137736. Trade-off: widens the host-root surface by one read-only command (low risk; read-only, fixed args). - Remove the
lxc-infodependency inguestInitPID(code fix, no new privilege): resolve the guest init PID without lxc-info — e.g. read it from the LXC cgroup /pctpidfile / a/procscan the non-root user can already do (it can read/proc/<pid>/mountinfo; only PID discovery is the gap). Cleaner long-term; keeps the allowlist tight. - Give
felhom-agenta writable$HOMEso directlxc-infoworks — weakest option (lxc-info on system containers still wants privilege for/run/lxclocks; #1 or #2 are the real fixes).
Also fix alongside (the §4 doubling): add mount --make-private /mnt/felhom-drives to the sudoers
FELHOM_INTERMEDIARY alias so EnsureSharedParent can isolate the parent's peer group.
Whichever path: this is a migration-completeness gap — the root→non-root cutover must carry the agent's full privileged-command set (here
lxc-info,make-private) into the sudoers, the same way the leaf/token/PBS-key issues were migration carry-over gaps.
7. Confidence + gaps
- Root cause: very high. Three independent confirmations: (a) guest mountinfo shows both drives;
(b) agent
/disksreports bothbound_under_parent=false; (c)lxc-inforeproducibly fails for thefelhom-agentuser (sudo-denied + homeless) yet works as root, and the code pathboundUnderParent→GuestSeesMount→guestInitPID→lxc-infois direct. - Not separately pinned: the exact controller-side trigger that re-clears
disconnectedbetween gate cycles (enroll/re-attach reconcile) — observed in logs but not traced to a single function; it governs the cadence of the flapping, not the root cause. guest_attached=Falseis also reported for both (a siblingGuestConfigpath); likely the same non-root regression family but viapct config, not traced here — surfaces in the report but the gate keys onBoundUnderParent, which is the proven culprit.- No reproduce performed (symptom already continuously live); no mounts/config/services changed.
No secrets recorded (the per-guest token used for the read-only /disks check is stored
out-of-band; never printed).
8. Outcome — FIXED 2026-06-29 (supervised sudoers completeness pass)
A full audit of every privileged command vs the allowlist (felhom-agent 55888e5; full table in
felhom-agent/REPORT.md) closed the gaps. Sudoers-only, no binary change, no agent restart.
Grants added: lxc-info -n [0-9]* -p -H (the root-cause fix) + mount --make-private /mnt/felhom-drives → FELHOM_INTERMEDIARY; systemctl restart dnsmasq → FELHOM_DNSMASQ; pct set [0-9]* -onboot 1 → FELHOM_PROVISION; pct reboot [0-9]* → FELHOM_GUESTHOOK. Deployed via the
install-script mechanism (visudo -cf → install -m 0440 -o root -g root → re-validate /etc/sudoers).
Validation (all pass): sudo -n lxc-info -n 9201 -p -H now returns PID 2137736; agent /disks
reports bound_under_parent=true for both felhom-usb + felhom-flash; the controller gate logged
drive RETURNED … re-attached + restarted for both and nextcloud auto-restarted (held healthy,
zero drive ABSENT after); the real UI (/settings) shows both drives Aktív concurrently. Symptom
gone.
Surfaced, then RESOLVED (2026-06-29, agent v0.45.0): GuestExec's general pct exec <vmid> -- …
(controller-swap / managed auto-update) needed arbitrary root-in-guest. Rather than grant it,
controller-swap's only shell vector (writeImage's bash -c "printf … >") was replaced by a
stdin-fed tee, making all 5 swap vectors fixed/read-only and allowlistable as FELHOM_CONTROLLERSWAP
(no bash -c, no general pct exec). Spike-proven (SPIKE-controllerswap-narrow-grants-2026-06-29.md)
- shipped; controller-swap works under the non-root agent again (live: 45/45 capability ok, negative
controls denied). Deferred:
sensors -j(no caller + lm-sensors not installed). Left as-is: the §4 host-side doubling (re-isolating the live parent would orphan the working guest slave — fixed grant takes effect on the next clean host boot). The §7 "guest_attached / pct config" note is resolved:GuestConfigis a Proxmox API call, not a sudo CLI command, soguest_attached=falseis not a sudoers gap (and is benign in the intermediary model).
Durable countermeasure SHIPPED (2026-06-29): the agent now self-probes its privileged
capabilities — felhom-agent v0.44.0 internal/capability (build-time manifest⊆sudoers test +
runtime sudo -n -l probe → HostReport.Capabilities) + hub v0.19.0 HostCapabilityChecker
(operator alert on a Critical grant going degraded). This exact incident's missing lxc-info grant
is now a CI build failure and a live capabilities self-check degraded signal, not a silent
multi-day regression. Live: agent reports 40/40 ok. See felhom-agent/REPORT.md.