Files
felhom.eu/documentation/audits/power-cut-fixes-2026-10-14
admin 3e2bdda721
gates / gates (push) Successful in 6m46s
R-1009 closed (estimator live, read back); R-1007: the operator's ruling recorded, and Immich v3.2.4 rebuilds single photos
Read-back: live sizing-data.json?v=4 byte-equal to the commit; the presets keep their verdicts,
Immich alone on 8 GB still tight; only the set without Immich changed. Spike B2 on 9202:
POST /api/assets/jobs regenerate-thumbnail rebuilt exactly the 10 broken assets (1.55 s, 5.3 s CPU),
0 of the other 20 photos' 40 files rewritten (control: all rewrote 40 of 40); an empty file maps to
its asset by the first 36 characters of its name. Immich removed through the product, no leftovers.
Register 134 -> 133 (opened 0, closed 1). STATUS D9/D10 ruled.

Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
2026-10-14 15:40:23 +02:00
..

Power-cut fixes 2026-10-14 — spike (Part C) and live proof (Part E)

Times UTC. Box: demo-hp (Tier 0). No key, token or password in this folder.

C1 — what the night's cut left (from audits/night-power-2026-10-13/R3/)

Item Value
left on disk /var/lib/vz/dump/vzdump-lxc-9201-2026_10_14-04_42_18.tar.dat (file, root:root 0644, 12.4 GB at the cut, 12.2 GB after the boot — the unwritten tail was lost) and vzdump-lxc-9201-2026_10_14-04_42_18.tmp/ (directory, root:root 0755). No .log (vzdump writes it at the end)
name pattern vzdump-lxc-<vmid>-YYYY_MM_DD-HH_MM_SS + .tar.dat / .tmp; the timestamp is the host's local time
the task UPID:demo-hp:000A8C2E:000DFB52:6ACEEC0A:vzdump:9201:felhom-agent@pve!agent: — started by the agent's API token
after the boot 02:52:56 the agent: „a vzdump backup is genuinely in-flight — leaving the lock" — so the dead task WAS in tasks?source=active 1 min after the boot. How long it stays: not recorded → C3
who may delete the files are root's in a root-owned directory; the agent runs as felhom-agent → it cannot delete them itself

C2 — predictions (written before the run)

Question Prediction
left on disk after kill -9 of the vzdump worker the same pair (.tar.dat + .tmp); the tar/zstd children may keep writing for a moment, then die on the broken pipe
StorageContent (backup) lists it no — Proxmox lists vzdump-*.tar.zst/.vma.zst etc.; .tar.dat is a temporary name
the active-task list keeps the task no — the PVE task daemon sees the worker die and ends the task with an error (unlike a power cut, where nobody ends it)
lock: backup on the guest stays (vzdump's cleanup did not run)
the next backup the space preflight sizes from the last COMPLETE archive (5.6 GB) and passes while space is free; the partial still occupies the disk

Backup pressed through the product 08:07:30 (POST /api/guest-backup/trigger, 200); at 1.5 GB written the vzdump worker (task UPID:…:vzdump:9201:…) was killed -9 at 08:09:44.

Question Prediction Measured
left on disk .tar.dat + .tmp yes — vzdump-lxc-9201-2026_10_14-10_08_05.tar.dat + .tmp/, root 0644 / 0755, no .log
the writers die on a broken pipe no — tar and zstd (children of a bash -c, none named „vzdump") kept writing for 4 min 40 s, to 5.6 GB (a whole archive's size), then exited. The file was never renamed. A „no vzdump process" check alone would call a file dead while it is being written
StorageContent lists it no no — only the complete …09_28_50.tar.zst is listed
active-task list keeps the task no no — gone within seconds (the PVE daemon ended it: „backup job failed" 08:09:46)
lock backup stays the agent's 10-min janitor cleared it at 08:10:18 while the orphan tar still read the snapshot; its snapshot delete failed („contains a filesystem in use") → lock: snapshot-delete; the next janitor pass (08:20:18) cleared that lock and removed the snapshot
the next backup passes the preflight none came: a failed manual press is not retried (the night loop retries; this one did not within 30 min)
clean-up — the 5.6 GB partial removed by hand at 08:41 (host root 78 % → 63 %)

C3 — one real power cut mid-write (9201's backup, pressed through the product)

Predictions (written before the cut):

Question Prediction
the dead vzdump task in tasks?source=active after the boot yes, and it stays there (nobody ends a task whose node died) — for the whole 15 min
BackupRunning (the agent's startup stale-lock line) true → „genuinely in-flight — leaving the lock"; Proxmox's start-all removes the lock anyway
the guest restart request (POST /api/server/reboot, the dashboard's) refused „backup running" while the dead task is listed
the household power-off request refused — „too soon" first (host up < 10 min), and on demo-hp „other guests" anyway (9202 and Tester 1 run); it cannot reach the backup check here
files .tar.dat + .tmp stay, nothing removes them

Measured (C3/): backup pressed 08:42:25; at 1.5 GB the cut: 08:44:08.089 off → 08:44:18.165 on (plug countdown). Boot btime 1791967481; the dead task's start 1791967345 (136 s before the boot).

Question Prediction Measured
dead task in the active list stays all 15 min listed RUNNING at 08:44:57; gone at 08:45:30 — Proxmox's own start-up clears it within ~30–60 s of the boot (at 10:45:00 local pvesh/pve-guests: „removed left over backup lock from '9201'!")
BackupRunning true true at the agent's start (08:44:59: „genuinely in-flight — leaving the lock") — the agent starts inside that short window
guest restart request (POST /api/server/reboot, 08:47:09) refused „backup running" 202 — it restarted 9201. The restart route never asks BackupRunning; the guestpower.go caller is the guest-power WATCHDOG (it starts stopped guests), not the restart
power-off request (08:48:41) refused 404 — demo-hp's 9201 runs controller 0.309.0, which has no power-off route; the guard can be exercised only after the floor raise (Part E)
files stay stay all 15 min: …10_42_25.tar.dat 1.16 GB (1.5 GB at the cut — the unwritten tail lost) + .tmp/
lock Proxmox removes it removed by Proxmox at boot

What this changes (STOP-rule check): the night's mechanism is the same — the partial is the damage, Proxmox does not list it as a backup. R-1008's window is short (≈ 1 min after a boot): the startup stale-lock pass, the first guest-power watchdog sweep and the controller supervisor in that minute see a dead task as running. D1 stays (one helper, cheap, correct); its live effect is small. D2 is the fix that matters.

C4 — a backup that runs out of space (prediction before the run)

On scratch guest 9202, vzdump 9202 --storage local --mode snapshot --compress zstd run as root on the host (9202 has no agent link; the question is vzdump's own behaviour), with the host root filled by a test file to ~600 MB free. Prediction: vzdump fails with „No space left on device" and removes its own .tar.dat/.tmp (its error path runs, unlike a power cut); the lock clears; nothing is left.

Measured (C4/, host root filled to 600 MB free by C4-fill-test.bin, removed after):

Run Result
vzdump 9202 (dir storage → vzdump's rsync path, suspend mode) „No space left on device" in the first sync; vzdump removed its .tmp; only vzdump-lxc-9202-…11_01_36.log (2 KB) left; space back
vzdump 9201 (LVM-thin → the snapshot + tar … | zstd > .tar.dat path, the night's path) zstd: error 70 … No space left on device; vzdump removed its .tar.dat; only the .log left. But the full disk broke LVM: „Failed to write metadata … Backup of volume group pve metadata failed"; the vzdump snapshot was half removed (disk-0's gone, disk-1's left) → lock: snapshot-delete
the agent's janitor after it 11:14:59 local: cleared the lock, then its snapshot delete failed („Failed to find logical volume pve/snap_vm-9201-disk-0_vzdump") — it would fail the same way every 10 min, the guest's snapshot list stuck. Fixed by hand 09:19 UTC: pct unlock 9201; pct delsnapshot 9201 vzdump --force (removed disk-1's snapshot LV, cleared the entry)

D3 decided by C4: vzdump cleans its own partial on ENOSPC, so the preflight's sizing (R-1006 part 2) costs only wasted time, never a leftover; spaceFits stays as it is. The real danger C4 shows is different: a host root that fills up breaks LVM's own metadata writes — the night's 12 GB partial took demo-hp to 92 %. Removing a dead partial (D2) is what keeps the root disk from filling. New finding: the janitor's snapshot delete has no --force path for a half-removed snapshot (row).

F — can Immich repair the power cut's damage by itself? (R-1007, spike only)

Prediction (before the run): Immich's thumbnail job with „missing" does NOT redo a zero-byte thumbnail (the database says it exists); the „all" (force) run does; nothing in Immich restores a deleted original, but a storage/integrity report lists it.

Measured (F/immich-repair.txt, F/immich-delete-then-missing.txt; Immich v3.2.4 on 9202, 30 photos, deployed and removed through the product):

Act Result
10 assets' thumbnail + preview truncated to 0 bytes; 1 original deleted (the night's damage, R2) the API serves 0-byte thumbnails/previews; the deleted original: 404
integrity jobs integrity-missing-files, -untracked-files, -checksum-mismatch (POST /api/jobs) the deleted original is found: GET /api/admin/integrity/report?type=missing_file lists it with its asset id. The zero-byte thumbnails are not reported (the checks cover originals)
thumbnails „missing" (PUT /api/jobs/thumbnailGeneration {force:false}) does not fix a zero-byte thumbnail — the database says it exists
zero-byte files deleted first, then „missing" still not fixed (Immich decides from its database, not the disk)
thumbnails „all" ({force:true}) fixes all 10 — every thumbnail regenerated (30 photos: 16 s; a family library of 50,000: hours of CPU)
the deleted original nothing in Immich restores it — only a backup

Prediction: right on „missing" and „all"; right that nothing restores an original; the integrity report exists in this version (/api/admin/integrity/…) and finds the missing original.

Proposal for R-1007 (an operator decision): after an unclean boot, run Immich's „integrity-missing-files" job and show its report (operator page, later the household), and run „thumbnails: all" in the night window. Cost: one catalog-level hook and CPU in proportion to the library; a missing original then needs a restore from the night backup. If nothing is done: broken previews stay until someone forces a regeneration by hand; a lost original stays unnoticed.

B2 — can Immich rebuild single photos? (R-1007, after the operator ruling „fix only the broken photos", spike only)

Immich v3.2.4 on scratch guest 9202, deployed and removed through the product; 30 bench photos (B2/).

What the running server answers (asked, not remembered): POST /api/assets/jobs with a bad body → 400 listing the names refresh-faces, refresh-metadata, regenerate-thumbnail, transcode-video; body {"assetIds": [<uuid>…], "name": "regenerate-thumbnail"}, permission AssetUpdate (an admin or the owner's key). In the server code (dist/services/asset.service.js run()): one AssetGenerateThumbnails job per id given — nothing else is queued.

Path layout: <upload>/thumbs/<user id>/<a[0:2]>/<a[2:4]>/<asset id>_thumbnail.webp and …_preview.jpeg (+ one .immich marker file). Mapping an empty file to its asset: the first 36 characters of the file name ARE the asset id. A box finds the broken photos from the disk alone: find <upload>/thumbs -type f -size 0, cut the names to 36 characters, unique.

Step Result
damage assets 1–5: thumbnail → 0 B; assets 6–10: preview → 0 B; the API served the 0-byte files
found from the disk 10 zero-byte files → 10 asset ids (the mapping above)
POST /api/assets/jobs regenerate-thumbnail for those 10 204; no zero-byte file left after 1.55 s; queue idle after 16.9 s (5 s polling); CPU 5.3 s (all Immich containers, cgroup usage_usec)
the 10 assets all 20 files rewritten (the undamaged sibling too), none empty; the API serves real thumbnails and previews
the other 20 assets 0 of 40 files rewritten (mtimes before/after)
positive control: thumbnails „all" (force=true) rewrote 40 of 40 of those same files (the 41st counted file is the .immich marker); CPU 16.5 s for 30 photos
teardown Immich removed through the product; no folder, container, volume or image left; 9202's standing apps untouched

Answer: yes — v3.2.4 rebuilds single photos. Cost ≈ 0.5 s CPU per photo here (a family library with 80 broken photos, like the night's R2: well under a minute) against „all" in proportion to the whole library. A lost original is still found only by integrity-missing-files and restored only from a backup.

E — the live proof on demo-hp (agent 0.156.1 + bundle, delivered by signed jobs)

Vouched by the operator 2026-10-14 (read back from /configuration): agent 0.156.1 83c7da27…, golden 0.310.0 2110d5e4…, min_agent 0.155.0, global floor 0.310.0. demo-hp: agent_update 0.156.1 (11:00 UTC) → agent_config_update 0.156.1-step1 (11:15, „written=1", probe 70/71 — the helper not yet there) → agent_config_update 0.156.1 (11:30, „BUNDLE WROTE /usr/local/sbin/felhom-vzdump-partial-clean (new)", probe 71/71). The new agent's first start (11:00) already FOUND C3's partial and called it dead — and left it, „the root helper refused: sudo: a password is required" (the helper came with the bundle). Real sudo after the bundle: E/sudo-live-demo-hp.txt (the two shapes allowed, six attacks refused).

Prediction (before the cut): after the boot the agent's start-up sweep removes BOTH partials (C3's from 08:44 and this one), logged „removed a dead half-written backup … written before this boot"; the dead vzdump task is logged once as „worker process is gone" and the start-up stale-lock pass clears the lock itself (or finds Proxmox already did); the next backup runs and succeeds; host root returns below its use before this cut.

Measured so far (11:42 UTC):

Step Result
floor 0.310.0 not delivered: each demo customer (demo-felhom, demo-hp, Tester 1) has its OWN floor 0.309.0 (set 2026-10-12 16:07), which wins over the global one (hub log + floor_raise_skipped mail). demo-hp's guest stays on 0.309.0
backup pressed (11:42:33 UTC) the pre-backup cleanup worked live: removed a dead half-written backup … vzdump-lxc-9201-2026_10_14-10_42_25.tar.dat (C3's 1.16 GB) and its .tmp, through the root helper (sudo … felhom-vzdump-partial-clean local 9201 …); each removal took ~7 s (the helper's /proc scan); then „space preflight passed" (E/agent-cleanup-before-backup.txt)
the backup itself failed in 1 s: lvcreate snapshot 'pve/snap_vm-9201-disk-1_vzdump' error: Failed to suspend thin snapshot origin; kernel: „device-mapper: thin: Creation of new snapshot 4 of device 2 failed" (E/failed-backup.log)
why the thin pool holds an orphan thin device 4 — a 19 GiB snapshot of 9201's data disk (device 2) created at pool time 138 — which LVM does not know (LVM: devices 1, 2; transaction 559 on both sides). It is what C4's full root disk left: the snapshot cleanup could not write LVM's metadata. Read with a metadata snapshot (thin_ls --metadata-snap, reserved and released; E/thin-ls.txt)
consequence no whole-guest backup of 9201 on demo-hp can run until device 4 is deleted (every snapshot create picks id 4). Caused by this session's C4 test

Repair (operator „B2 - A", 12:02 UTC): dmsetup message pve-data-tpool 0 "delete 4" → thin_ls shows devices 1 and 2 only, pool transaction 559 unchanged, LVM agrees (E/repair.txt). Identity, not size: device 4 was the one id LVM does not know, a snapshot (CREATE_TIME 138) of device 2.

The cut (E/): fences clear 12:04:48; backup pressed through the product 12:04:48 (POST /api/guest-backup/trigger, 200); the apps were stopped for the snapshot and started again at 12:05:30 (snapshot create worked — the repair held); at 1.53 GB written, plugcut.py --plug hp --off-seconds 10: 12:06:52.325 off → 12:07:02.409 on (plug countdown; watchdog not needed). Root use at the cut 26,249,101,312 B; before the press 24,720,375,808 B. SSH back 12:07:45.

Prediction Measured
dead task logged once as „worker process is gone" yes — 12:07:44 „backup-in-flight: a vzdump task is listed as active but its worker process is gone — it is dead, not running" (the agent started 12:07:42, inside the 30–60 s window in which Proxmox still lists the dead task — the R-1008 case, now judged right)
the start-up stale-lock pass clears the lock, or finds Proxmox did both, in the same second: the agent decided „clearing a stale backup lock" and ran pct unlock 9201; Proxmox's start-all had just removed it („removed left over backup lock from '9201'!", 12:07:44) → the agent logged ERROR „pct unlock failed … no lock found". Harmless (the guest started 12:07:47), but an ERROR for an outcome that is the goal — R-1013
the start-up sweep removes the partial yes — 12:07:48 „removed a dead half-written backup … vzdump-lxc-9201-2026_10_14-14_05_21.tar.dat 964,239,360 B (written before this boot)" and 12:07:52 its .tmp — through the root helper. 1.53 GB were written before the cut, 0.96 GB survived (the unwritten tail lost, as in C1)
guards do not refuse because of the dead task the start-up sweep IS a BackupRunning caller: it went ahead only because the dead task was judged not running. The household power-off request at 12:09:53 (uptime 147 s) was refused too_soon_after_boot only (E/requests.txt). The guest restart is not guarded by BackupRunning at all (C3)
the next backup succeeds yes — pressed 12:10:06, „found old vzdump snapshot (force removal)" (vzdump removed the cut's LVM snapshot itself), archive vzdump-lxc-9201-2026_10_14-14_10_50.tar.zst 5,650,197,172 B at 12:17:08; the older archive pruned (keep-last 1) (E/next-backup.txt)
host root back below its use before this cut yes — 24,689,217,536 B after the boot sweep; 24,691,134,464 B after the new backup (before the press: 24,720,375,808 B)
no orphan thin device this time yes — pool transaction 567, LVM lists only vm-9201-disk-0/1

E/demo-hp-journal-boot.txt is the whole boot journal (scanned: no key, token or password). R-1006 and R-1008 are proven live on demo-hp.

Delivery to the other two boxes (E/delivery-tester1-n100.txt): signed jobs agent_update 0.156.1 (12:21:24 UTC) → agent_config_update 0.156.1-step1 (12:35:37) → agent_config_update 0.156.1 (12:50:14). Tester 1: agent 0.156.1 committed 12:24, bundle done 12:53 (probe 71/71); N100: agent 12:34 (it reports every 15 min), bundle done 13:04 (probe 71/71). On both, real sudo -l -U felhom-agent allows the cleanup shape and refuses a finished archive (.tar.zst). The operator cleared the per-customer floors (demo-hp and demo-felhom 12:00, Tester 1 12:44 UTC): every guest runs controller 0.310.0. Both plugs ON: the HP relay back on by its countdown (12:07:02, E/plugcut.log), the N100's never cut; both hosts answer.