Skip to content

fix(vm): speak donate-clanker's version-2 bootstrap protocol in the guest - #40

Merged
castrojo merged 1 commit into
mainfrom
fix/vm-bootstrap-enablement
Aug 1, 2026
Merged

fix(vm): speak donate-clanker's version-2 bootstrap protocol in the guest#40
castrojo merged 1 commit into
mainfrom
fix/vm-bootstrap-enablement

Conversation

@castrojo

@castrojo castrojo commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

The defect

The published podman-vm guest boots and the worker never runs.

The suspected cause going in was an unapplied systemd preset. That is not the defect. Evidence from the real published v25.08.15 aarch64 disk:

  • debugfs on the root partition shows /etc/systemd/system/multi-user.target.wants/donate-clanker-bootstrap.service already present, pointing at /usr/lib/systemd/system/donate-clanker-bootstrap.service. FSDK's files/vm/prepare-image.sh, which podman-vm-efi.bst invokes, runs systemctl --root "${sysroot}" preset-all at image assembly time. The preset mechanism works.
  • Booting the disk under QEMU and logging in: donate-clanker-bootstrap.service is UnitFileState=enabled, LoadState=loaded, ActiveState=failed, Result=exit-code.
  • systemd-networkd.service is enabled/active/running; networkctl shows the NIC routable with a DHCP lease of 10.0.2.15/24, and systemd-networkd-wait-online.service finished. Networking was never broken and is left alone.

The journal has the actual cause:

systemd[1]: Starting donate-clanker-bootstrap.service - Donate-clanker guest bootstrap worker...
donate-clanker-bootstrap[438]: donate-clanker bootstrap failed: Expecting value: line 1 column 1 (char 0)
systemd[1]: donate-clanker-bootstrap.service: Main process exited, code=exited, status=1/FAILURE

The guest consumer speaks protocol version 1. donate-clanker speaks version 2. Concretely, the shipped consumer:

  1. required the envelope key set to equal exactly {version, hive_endpoint, registration_token, backend, run_id}, so a version-2 envelope carrying goose_provider, goose_model or provider_secret was rejected;
  2. rejected any version that was not 1;
  3. acked {"version": 1, ...}, which the host rejects because it requires {"version": 2, "type": "control_ack"};
  4. treated an empty first read as fatal, which is exactly the crash above;
  5. exported DONATE_CLANKER_* environment names, while the pinned worker reads HIVE_WS_URL, HIVE_HUB, HIVE_REGISTRATION_TOKEN, AGENT_BACKEND, GOOSE_PROVIDER, GOOSE_MODEL and GITHUB_COPILOT_TOKEN.

Any one of those alone makes the VM path inert.

The fix

elements/podman-vm/files/donate-clanker-bootstrap.py now:

  • speaks protocol version 2 and acks {"version": 2, "type": "control_ack"};
  • validates only the four required fields and tolerates optional/unknown keys, so donate-clanker can extend the envelope without breaking the guest;
  • retries the port open and empty reads under a bounded 240s deadline instead of dying on the first EOF;
  • execs the worker with the environment names the worker actually reads, defaulting GOOSE_PROVIDER to github_copilot and mapping provider_secret to GITHUB_COPILOT_TOKEN;
  • mirrors progress lines to /dev/kmsg as well as stderr.

The unit gains TimeoutStartSec=300 so systemd's 90s default cannot kill a wait that must outlast the host's 180s accept timeout.

The /dev/kmsg mirroring is not decoration. Measured on the real disk: a unit's StandardOutput=journal+console stops reaching the serial console once serial-getty@ttyAMA0.service has run its TTYVHangup=yes, while a /dev/kmsg write from the same boot lands and is timestamped like a kernel message. kmsg is the only reliable channel for a boot-time assertion on this guest. Kernel audit lines are no better, since they route to auditd once it starts, before the bootstrap unit runs.

No new enablement mechanism was added, because none is missing.

The test

tests/vm-boot.sh gains marker 5: the serial log must contain the bootstrap's first banner line. That proves the unit shipped, the preset enabled it, systemd activated it and ExecStart ran, without requiring a handshake CI cannot complete (nothing in CI writes an envelope, and the envelope schema belongs to donate-clanker, so asserting on it here would make their bump redden our builds). The unit still goes on to fail its wait in CI; that is expected and documented.

The assertion is not vacuous. Run against the unfixed published v25.08.15 disk:

FAIL: guest did not reach its ready point within 500s: the serial console is missing
the login prompt, the donate-clanker-bootstrap.service banner, or both

with login: present once in the captured serial log and donate-clanker-bootstrap present zero times.

Proof

The fixed script was injected into the real booted v25.08.15 guest, the shipped v1 unit stopped, and a version-2 envelope served over the virtio-serial port. The host observed exactly one ack line:

{"version": 2, "type": "control_ack"}

and the serial console showed both kmsg banners:

[  216.575254] donate-clanker-bootstrap: waiting for the host bootstrap envelope on /dev/virtio-ports/org.projectbluefin.donate-clanker.bootstrap
[  216.592294] donate-clanker-bootstrap: envelope accepted for run probe-run-1, starting the worker

with the worker exec'd carrying HIVE_WS_URL, HIVE_HUB, HIVE_REGISTRATION_TOKEN, AGENT_BACKEND=goose, GOOSE_PROVIDER=github_copilot, GOOSE_MODEL and GITHUB_COPILOT_TOKEN.

No new disk was built. just export-podman-vm is a multi-GB BuildStream export that was not feasible here, so the runtime proof above comes from injecting the fixed script into the real guest rather than from booting a rebuilt image. Only a real CI build can confirm that the rebuilt image ships the fixed script and that marker 5 passes green.

Validation

  • just validate exits 0 and resolves through podman-vm/podman-vm-efi.bst
  • shellcheck tests/vm-boot.sh, bash -n, git diff --check clean
  • python3 -m py_compile and ruff check clean
  • envelope validation exercised locally: v2 full and minimal accepted, unknown optional keys tolerated, v1 and missing run_id rejected with clear messages

Scope

One logical change. No Lima, cloud-init, SSH or guest agent reintroduced. PR #38's publish guarantees and PR #39's boot assertions are untouched; marker 5 is additive. Docs in docs/skills/ are corrected in the same PR, since they previously carried the false "the preset is not applied, do not assert on the unit" claim.

Unblocks projectbluefin/donate-clanker#13.

…uest

The published podman-vm guest boots, systemd activates
donate-clanker-bootstrap.service, and the service then dies immediately:

  systemd[1]: Starting donate-clanker-bootstrap.service ...
  donate-clanker-bootstrap[438]: donate-clanker bootstrap failed:
      Expecting value: line 1 column 1 (char 0)
  systemd[1]: donate-clanker-bootstrap.service: Main process exited,
      code=exited, status=1/FAILURE

The user-visible symptom is that the VM boots and the worker never runs.

The guest consumer spoke protocol version 1: it required an exact key set,
rejected anything whose version was not 1, acked version 1, treated an
empty first read as fatal, and exported DONATE_CLANKER_* environment
names. donate-clanker sends a version-2 envelope with optional keys,
requires a version-2 ack, and its worker reads HIVE_WS_URL, HIVE_HUB,
HIVE_REGISTRATION_TOKEN, AGENT_BACKEND, GOOSE_PROVIDER, GOOSE_MODEL and
GITHUB_COPILOT_TOKEN. Every real envelope was rejected twice over, and
even a hypothetically accepted one would have started a worker with no
credentials in its environment.

Rewrite the consumer to speak version 2, validate only the four required
fields while tolerating optional ones, retry the port open and empty
reads under a bounded deadline, ack version 2, and export the names the
worker actually reads. Give the unit a start timeout long enough to
outlast the host's accept timeout.

Progress lines are mirrored to /dev/kmsg. Measured on the real disk, a
unit's StandardOutput=journal+console no longer reaches the serial
console once serial-getty has run its TTYVHangup, while /dev/kmsg writes
still land, so kmsg is the only reliable channel for a boot-time
assertion here.

tests/vm-boot.sh gains marker 5, asserting that the bootstrap banner
reaches the serial console. That proves the unit shipped, the preset
enabled it, systemd activated it and ExecStart ran, without requiring a
handshake that CI cannot complete. Run against the unfixed published
v25.08.15 disk the new assertion fails as intended: the login prompt
appears, the banner never does.

The brief's suspected cause -- an unapplied systemd preset -- is not the
defect. FSDK's files/vm/prepare-image.sh runs systemctl --root preset-all
at image assembly time, the published disk does contain
/etc/systemd/system/multi-user.target.wants/donate-clanker-bootstrap.service,
and systemd-networkd is enabled and takes a DHCP lease. The docs that
claimed otherwise are corrected here.

Unblocks projectbluefin/donate-clanker#13.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@castrojo
castrojo merged commit 2ee694e into main Aug 1, 2026
5 checks passed
@castrojo
castrojo deleted the fix/vm-bootstrap-enablement branch August 1, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant