Validate the AVF control socket path, and publish to crates.io from CI - #6
Merged
Merged
Conversation
The QEMU backend already rejects a QMP socket path that won't fit in `sockaddr_un.sun_path` before handing it to QEMU. The AVF backend has the same exposure and hits it sooner: `avf-control.sock` is 8 bytes longer than `qmp.sock`, so the limit lands at a shorter VM name. The consequence is worse on the AVF side. agv tells the runner where to bind, and every later `stop`, `suspend`, and `status` connects to that path. An over-long one leaves a booted VM that nothing can control, with the failure surfacing as an opaque RPC timeout well after start returned. Extracted the existing check into `vm::ensure_socket_path_fits` rather than copying the `SocketAddr::from_pathname` call, and called it from both backends — QEMU where it already was, AVF at the top of `start` before the runner config is written or the runner spawned. The unit test doesn't pin a byte count: the limit is platform-defined (104 on macOS, 108 on Linux), which is the reason the check delegates to `from_pathname` instead of hardcoding one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two claims in the install section were wrong once the AVF backend became the macOS default. The crates.io entry sat alongside the install script as though the two were equivalent. They are not: `cargo install` can only lay down cargo's own bin targets, and `agv-avf-runner` is a Swift binary, so on macOS Apple Silicon a cargo install produces an `agv` whose default backend has no runner to spawn. That is a property of cargo, not an oversight to fix later, so the README now says which platform gets a complete tool from which channel. The from-source paragraph then claimed that without the sibling runner "`agv create` falls back to QEMU". No such fallback exists. `config::default_backend` returns "avf" on macOS aarch64 unconditionally, and `locate_avf_runner` is only reached from `LocalAvfBackend::start` — so `agv create` builds the VM and then fails to boot it. Someone following the old text would conclude their install was broken in some other way. It now describes the real behaviour, and points at `agv doctor` and `--backend qemu`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
crates.io was at 0.3.0 while install.sh served 0.4.1, because the release workflow only built binaries and cut the GitHub release — `cargo publish` was a manual step nobody remembered. The README advertises `cargo install agv`, so the drift was user-visible. The job runs last in the DAG, after `release`. Publishing a version is irreversible (yank hides it, nothing deletes it), so it should not happen until all three build legs are green and the GitHub release exists; a failed macOS leg must not leave a crates.io version pointing at a release that was never cut. Auth is crates.io Trusted Publishing (RFC 3691) rather than a stored API token. GitHub proves the workflow's identity over OIDC and crates-io-auth-action exchanges that for a token valid ~30 minutes, revoked by its post step when the job ends. A long-lived registry secret would be a standing credential readable by anything running in the job, with a blast radius of everyone who runs `cargo install agv`. Nothing long-lived is stored, so there is nothing to leak or rotate. The auth step is gated on the same condition as the publish, so a run that skips publishing never mints a token at all. Two guards in the decide step. Pre-release tags skip: crates.io would accept a semver pre-release and `cargo install` would correctly ignore it, but the version number is then burned forever for a build whose only audience is the tarball channel. Already-published versions skip too, which makes a workflow re-run on the same tag a no-op instead of a hard failure, so a transient upload error is retryable from the Actions UI. Publishing only ships `agv`; cargo cannot carry the Swift runner. The release docs move with it. CONTRIBUTING.md stated the opposite policy outright — "`cargo publish` runs from a trusted local machine, not CI, on purpose" — and both it and `release-check.sh` listed a manual `cargo publish` as the last step, which would now double-publish or fail on a version CI had already uploaded. The ordering is the part worth writing down: the tag push is the point of no return, which makes the dry-run in `release-check.sh` the last chance to catch a crate that won't package. Two details that bite if missed, noted in both files. Naming any entry in `permissions` drops the rest to none, so `contents: read` is spelled out alongside `id-token: write` or checkout fails. And crates.io trusts the repo by (owner, repo, workflow filename), so renaming release.yml breaks publishing until the trusted publisher is updated to match. `cargo publish --locked` pins the published build to the committed Cargo.lock. Check 4 of `release-check.sh` already fails a release whose lock disagrees with Cargo.toml, so this asserts something the flow guarantees rather than introducing a way to fail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
einarfd
force-pushed
the
avf-socket-path-and-crates-io
branch
from
August 27, 2026 20:06
7b8e6b5 to
ea3a350
Compare
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.
Three unrelated changes that came out of the same review pass.
Validate the AVF control socket path length
The QEMU backend already rejects a QMP socket path that won't fit in
sockaddr_un.sun_path. The AVF backend had the same exposure and hits itsooner —
avf-control.sockis 8 bytes longer thanqmp.sock, so the limitlands at a shorter VM name.
The consequence is worse on the AVF side. agv tells the runner where to bind,
and every later
stop,suspend, andstatusconnects to that path. Anover-long one leaves a booted VM that nothing can control, with the failure
surfacing as an opaque RPC timeout well after
startreturned.Extracted the existing check into
vm::ensure_socket_path_fitsrather thancopying the
SocketAddr::from_pathnamecall, and called it from both backends— QEMU where it already was, AVF at the top of
startbefore the runner configis written or the runner spawned. The unit test deliberately doesn't pin a byte
count; the limit is platform-defined (104 on macOS, 108 on Linux), which is why
the check delegates to
from_pathnameinstead of hardcoding one.README corrections
Two claims were wrong once AVF became the macOS default.
The crates.io entry sat alongside the install script as though they were
equivalent.
cargo installcan only lay down cargo's own bin targets, andagv-avf-runneris a Swift binary, so on macOS Apple Silicon a cargo installproduces an
agvwhose default backend has no runner to spawn. That's aproperty of cargo, not something to fix later.
The from-source paragraph then claimed that without the sibling runner
agv create"falls back to QEMU". No such fallback exists —config::default_backendreturns"avf"on macOS aarch64 unconditionally andlocate_avf_runneris only reached fromLocalAvfBackend::start, so createbuilds the VM and then fails to boot it. Now describes the real behaviour and
points at
agv doctorand--backend qemu.Publish to crates.io from the release workflow
crates.io was at 0.3.0 while
install.shserved 0.4.1, becausecargo publishwas a manual step nobody remembered. The README advertises
cargo install agv,so the drift was user-visible.
The new
publishjob runs last in the DAG, afterrelease. Publishing isirreversible (yank hides it, nothing deletes it), so it shouldn't happen until
all three build legs are green and the GitHub Release exists — a failed macOS
leg must not leave a crates.io version pointing at a release that was never cut.
Auth is crates.io Trusted Publishing (RFC 3691), not a stored API token.
GitHub proves the workflow's identity over OIDC and
crates-io-auth-actionexchanges that for a token valid ~30 minutes, revoked by its post step when the
job ends. A long-lived registry secret would be a standing credential readable
by anything running in the job, with a blast radius of everyone who runs
cargo install agv. The auth step is gated on the same condition as thepublish, so a run that skips publishing never mints a token at all.
Two guards in the decide step:
cargo installwould correctly ignore it, but the version number is thenburned forever for a build whose only audience is the tarball channel.
a no-op instead of a hard failure, so a transient upload error is retryable
from the Actions UI.
The release docs move with it.
CONTRIBUTING.mdstated the opposite policyoutright ("
cargo publishruns from a trusted local machine, not CI, onpurpose") and both it and
release-check.shlisted a manualcargo publishasthe last step, which would now double-publish or fail on a version CI had
already uploaded.
Two details that bite if missed, noted in both files: naming any entry in
permissionsdrops the rest to none, socontents: readis spelled outalongside
id-token: writeor checkout fails; and crates.io trusts the repo by(owner, repo, workflow filename), so renaming
release.ymlbreaks publishinguntil the trusted publisher is updated to match.
Testing
cargo clippy --all-targetsclean, fullcargo testgreen (418 lib testsplus integration).
cargo publish --dry-run --lockedpackages clean (101 files, 371 KiBcompressed).
bash -n.0.3.0 → 200(skip),0.4.1 → 404(publish).The publish path itself is untested until a real tag — cutting 0.4.2 is the way
to exercise it. crates.io will jump from 0.3.0 to that version; 0.4.0 and 0.4.1
stay unpublished. Cosmetic, since nothing depends on
agvas a library.🤖 Generated with Claude Code