fix(appliance): rebuild a container store left corrupt by an interrupted write (#1029) - #1032
Merged
Merged
Conversation
…ted write (#1029) An unclean reset partway through the first-boot image load leaves containers/storage with zero-length `lower` files. containers/storage splits an empty-but-present `lower` on ":" into one EMPTY element, joins that onto the graph root and readlinks the resulting directory, so every container start dies with "readlink <graphroot>/overlay: invalid argument". Both guards in load_baked_images still passed — the digest record matched and `image exists` returned true — so the archive was never reloaded and the damage survived every later boot. The wizard could not start its container, nothing listened on :80/:443, and the console sat on "preparing the setup page" indefinitely: an appliance that could not be installed from its own stick, with no error anywhere the operator could see. Verified on the physical bench: the store held seven zero-length `lower` files, every `podman run` failed with exactly that error, and wiping the store + reloading the baked archive brought the wizard up and serving. The check keys on the defect itself — a correct base layer carries NO `lower` file, so a zero-length one is damage and never a legitimate state — and leaves a healthy store untouched, so a provisioned machine is never made to re-pull for nothing. This makes the failure self-healing; it does not address what interrupted the write in the first place, which is filed separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1029.
Validated end to end on the physical HP bench today: a stick built from this branch installed the appliance to its internal NVMe and brought the full stack up (8 containers). That is the flow #1029 said was impossible.
The reported root cause was wrong
The issue blamed an unguarded
mkdir -p "$PWD/data"against a read-only root. That is a reproduction artifact, and the evidence is in the issue's own transcript:/opt/pithead/datadoes not exist on the box, and the unit runsExecStart=/data/pithead/pitheadwithWorkingDirectory=/data/pithead— a real rw ext4 mountpoint (pithead-mount-generatormounts partition 4 of the disk the system booted from, which on a stick boot is the stick's own).mkdir -p "$spool"atpithead:2066, would have aborted before theLoading this build's container imagesline the transcript shows./data/pithead/data/firstbootexists on disk owned1000:1000. It succeeded.mkdir -preports the deepest component it could not create, so both writes produce a byte-identical message. The string alone cannot identify the line.The by-hand repro was run from
/opt/pithead, which is not where the service runs.What actually breaks it
Running the wizard as the unit does, and a bare
podman runwith no wizard involved, both give:The store held seven zero-length
lowerfiles. containers/storage splits an empty-but-presentloweron:into one empty element, joins it onto the graph root and readlinks that directory — hence the error namingoverlayitself. A correct base layer carries nolowerfile (f2ec4de…correctly had none), so zero-length is damage, never valid. Signature of an interrupted write.Why it was permanent — the actual product defect.
load_baked_imageshad two guards: the digest record matched, andimage existsreturned true (the image was there, just unrunnable). Both passed, so the archive was never reloaded and one unclean reset bricked the medium across every later boot, with nothing on the console.The fix
Detect the defect before trusting any digest record, tear the store down, reload. Keyed on the defect itself, so a healthy store is untouched and a provisioned machine never re-pulls for nothing.
Proven by live repair before writing it: wiping the store and reloading brought the wizard up on the bricked box.
Verification
make linttests/stackScope
Does not address what interrupted the write — filed as #1030 (the journal is
volatile, so the failing boot's evidence was gone; that is #1030's first recommendation). The unguarded$PWDwrites atpithead:2066/6605remain real latent bugs, just not this one.Base is
develop-v2; retarget before merge if auto-close is wanted.