Secret Scan / scan (push) Successful in 4s
Build App (Preview) / compute-version (pull_request) Successful in 5s
Secret Scan / scan (pull_request) Successful in 6s
Build App (Preview) / create-release (pull_request) Successful in 1s
Build App (Preview) / build-macos (pull_request) Successful in 2m55s
Build App (Preview) / build-linux (pull_request) Successful in 4m56s
Build App (Preview) / build-windows (pull_request) Successful in 5m53s
Build App (Preview) / prune-previews (pull_request) Successful in 1s
A project that was merely stopped reported "This project has no container or snapshot image yet, so there is nothing to compare against the base image" — with its container sitting right there — and Update stayed disabled. Start it and the checks passed, which is the tell: the staleness probe had only two sources, a *running* container via `docker exec` or the project's snapshot image. The snapshot is not a checkpoint. `commit_container_snapshot` runs only before a container is destroyed (a config-change recreate) or inside a migration, never on stop, so a project in daily use for a year can have no snapshot at all — and five of the six projects on the box that reported this had none. Absence of a snapshot was being read as absence of anything to inspect. So probe the stopped container directly: commit its writable layer to a throwaway image, probe that, drop it. A stopped container now also outranks the snapshot, for the same reason a running one already did — the snapshot lags it by everything installed since the last commit. `pick_probe_source` is the whole decision and is unit-tested; the message it used to emit now describes only the case it is true of, no container and no snapshot. Two things found on the way, both documented in CLAUDE.md: `bollard` never hands back the image id from a commit — its `Commit` model deserialises "ID" while the daemon sends "Id" — so the probe image has to be tagged, and a tagged image is dangling-proof and therefore invisible to `sweep_orphaned_snapshots`, `reap_stale_migration_pins` and `scrub_secrets_from_snapshots` alike. Without a reaper of its own a crashed probe would leak a multi-gigabyte image that nothing could ever reclaim, so `reap_probe_images` runs at startup beside `reap_probe_containers`, age-gated for the same reason that one is: `reference=` is daemon-wide and a second instance's live probe matches the glob. It removes by tag, never by image id: a force removal by id untags an image everywhere, which is how a first draft of the reaper test deleted an unrelated `alpine:latest`. Names are unique per call rather than stable per container, because container ids do not survive a recreate and two overlapping probes would otherwise fight over one tag. Verified against the container that reported the bug: 13,365 paths and an apt delta of cmake, ffmpeg, libobs-dev, qt6-base-dev and nine more — the migration payload the Update flow could not see. 546 unit tests plus three live-Docker tests pass; no new clippy warnings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RSaoDLovVV2wmH4H8VVxz