fix(deploy): identify agent images by build stamp, not image ID
ci / gates (push) Failing after 6s
ci / rust (push) Skipped
ci / frontend (push) Skipped
ci / e2e (push) Skipped
ci / publish (push) Skipped

The post-transfer verification added in bb34ef1 failed every deploy: it
compared `.Id` between build host and target, and a BuildKit image on the
build host carries attestation manifests that `docker save | docker load`
does not reproduce. The same build legitimately arrives with a different
Id and a different reported Size — tank had agent-base:dev at 28 MB /
363f23b7, gw-04 at 74 MB / edd46f95, both from the identical build.

`.Created` comes from the config blob, survives the round trip unchanged,
and is what actually answers "is the new build here". Both hosts reported
2026-08-02T16:43:00.599937575-07:00, which is how the false positive was
identified rather than assumed.

The verification itself stays — the truncation it guards against is real.
This corrects what it compares.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-02 19:02:08 -07:00
co-authored by Claude Opus 5
parent ec85f6c8da
commit 2380c2cb0b
+15 -8
View File
@@ -51,24 +51,31 @@ load() {
# truncated stream can still leave a partially-populated image, and shipping a # truncated stream can still leave a partially-populated image, and shipping a
# corrupt agent image to every fleet node is worse than failing the deploy. # corrupt agent image to every fleet node is worse than failing the deploy.
# One retry, because the observed failure is a transient stream stall. # One retry, because the observed failure is a transient stream stall.
#
# Identity is the image's `Created` stamp, NOT its `Id`. A BuildKit image on
# the build host carries attestation manifests that `docker save | docker load`
# does not reproduce, so the same build legitimately arrives with a different
# Id and a different reported Size — comparing Ids fails every transfer of a
# correctly-shipped image. `Created` comes from the config blob, survives the
# round trip, and is what actually answers "is the new build here".
load_if_changed() { load_if_changed() {
local lid rid attempt local lts rts attempt
lid=$(ssh "$BUILD_HOST" "docker image inspect -f '{{.Id}}' $1 2>/dev/null" || true) lts=$(ssh "$BUILD_HOST" "docker image inspect -f '{{.Created}}' $1 2>/dev/null" || true)
rid=$(ssh "$2" "docker image inspect -f '{{.Id}}' $1 2>/dev/null" || true) rts=$(ssh "$2" "docker image inspect -f '{{.Created}}' $1 2>/dev/null" || true)
if [ -n "$lid" ] && [ "$lid" = "$rid" ]; then if [ -n "$lts" ] && [ "$lts" = "$rts" ]; then
echo " (unchanged — skip)" echo " (unchanged — skip)"
return 0 return 0
fi fi
for attempt in 1 2; do for attempt in 1 2; do
load "$1" "$2" || echo " (transfer attempt $attempt failed)" load "$1" "$2" || echo " (transfer attempt $attempt failed)"
rid=$(ssh "$2" "docker image inspect -f '{{.Id}}' $1 2>/dev/null" || true) rts=$(ssh "$2" "docker image inspect -f '{{.Created}}' $1 2>/dev/null" || true)
if [ -n "$lid" ] && [ "$lid" = "$rid" ]; then if [ -n "$lts" ] && [ "$lts" = "$rts" ]; then
[ "$attempt" -gt 1 ] && echo " (recovered on attempt $attempt)" [ "$attempt" -gt 1 ] && echo " (recovered on attempt $attempt)"
return 0 return 0
fi fi
echo " (image ID mismatch after attempt $attempt — retrying)" >&2 echo " (build stamp mismatch after attempt $attempt — retrying)" >&2
done done
echo " ✗ $1 did not land on $2 (wanted ${lid:0:19}, got ${rid:0:19})" >&2 echo " ✗ $1 did not land on $2 (wanted $lts, got ${rts:-nothing})" >&2
return 1 return 1
} }