Skip to content

fix(manager): hold a cached agent to the digest its release installed it with - #860

Draft
antoinetoussaint-byte wants to merge 2 commits into
mainfrom
fix/cache-held-to-release-digest
Draft

antoinetoussaint-byte wants to merge 2 commits into
mainfrom
fix/cache-held-to-release-digest

Conversation

@antoinetoussaint-byte

@antoinetoussaint-byte antoinetoussaint-byte commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

What this adds

v0.19.8 (#819) made the download side verifiable: Download holds the archive to the release's digest (producer *checksums.txt first, GitHub's record second, a visible warning when there is neither) and GitHubReleaseToken is the one, exported credential rule (GITHUB_TOKEN, else GH_TOKEN, never a gh login), named in the anonymous-404 error. This PR completes the install side that #819 did not carry:

  • Download records the installed executable's SHA-256 in a dot-prefixed sidecar beside it (.<name>__<version>.sha256, sha256sum format). The dot keeps it out of every cache scan (findLocalLatestInDir, Installed, the CLI's localCacheVersions); tested.
  • Load → admitCachedRelease: a cached executable with a record must match it or is refused by name and both digests. No record + CODEFLY_AGENT_SOURCE=local → admitted (that is what the developer asked for). No record + remote resolution → the release is downloaded over it; if that fails the cached bytes are refused, naming the CODEFLY_AGENT_SOURCE=local escape. Gated to kinds that resolve from GitHub releases with AutoDownload, so providers and modules are untouched.
  • InstalledReleaseDigest(ctx, agent) exposes the record ("", no error, for a cache entry never installed from a release) for future adoption by the CLI's agents versions listing and publish verification — no cli consumer exists yet (cli main 1901fbc6).

Why

The cache is keyed by name alone, so a build left under a released version's name was run by every process resolving that version with nothing to tell it from the release. parse__0.1.1 tonight was exactly that: a hash-different local build.

Consequence to know before merging

Every pre-existing cache entry has no sidecar, so the first load after this upgrade re-downloads each release-resolved agent once (under remote resolution). Under CODEFLY_AGENT_SOURCE=local nothing changes.

Provenance

Folded from the parked handoff branch fix/release-token-and-digest @ 6b804b8a (based on b71ee8cd, pre-#819). Its token-rule and archive-digest halves were already on main through #819 with the same names (GitHubReleaseToken, FetchReleaseAsset/ReleaseAsset, DigestFromChecksums/DigestFromGitHub, checkArchiveDigest), so those files take main's version; this PR is the 195-line remainder (downloader.go, downloader_test.go, loader.go). One adaptation: readReleaseDigest uses main's two-value parseChecksums. The handoff branch is left in place, unmodified (no force-push).

Consumers

codefly-dev/cli would adopt InstalledReleaseDigest in cmd/agents/versions.go localCacheVersions and cmd/publish/verify.go (future adoption; nothing on cli main consumes it today), pinning core at the tag this lands in, never this branch's SHA.

Verified

go build ./..., go vet ./agents/manager/, go test ./agents/manager/ -count=1 ok (15 s), including TestReleaseDigestSidecarIsInvisibleToCacheScans and TestAdmitCachedReleaseHoldsTheCacheToItsRecord. Full go test ./... running locally at the time of opening; CI is the gate.

🤖 Generated with Claude Code


Draft: CI finding (2026-10-27 23:12Z)

The Build job fails in sdk, services, toolbox/lang, toolbox/launch, toolbox/session (locally reproduced; runners/golang red locally only under GOFLAGS=-mod=mod, green without it). Every failure is the same shape: the test plants a locally built stub under a released version's name in a fresh CODEFLY_HOME and loads it under remote resolution; admitCachedRelease finds no sidecar, tries the release (GET …/releases/tags/v71.0.0: 404) and refuses with the CODEFLY_AGENT_SOURCE=local escape. The admission rule works as designed; what it exposes is that core's own harness, and codefly agent build → run without --local-agents, rely on the cache admitting a local build by name.

That is a behaviour decision, not a test fix: either (a) tests and agent build flows set CODEFLY_AGENT_SOURCE=local (the deliberate-local-build rule, as this PR states it), or (b) a cache entry with no sidecar stays admitted and only a mismatching sidecar refuses, with the re-download reserved for entries that carry a record. Marked draft until the owner picks; (b) keeps agent build working unchanged and still closes the parse__0.1.1 hole for every agent installed by this downloader.

Review finding 1 addressed — head 457a6bd1afe0cd9c6768afb395269b9abe272f86

A local rebuild over a released name left the release sidecar beside bytes it no longer described, and the mismatch refusal ran before the local-source choice, so --local-agents could not run it. Now CODEFLY_AGENT_SOURCE=local is decided first: the build is admitted and the stale record removed (a later remote load then fetches the release instead of trusting the record). The remote refusal names the local escape. TestAdmitCachedReleaseHoldsTheCacheToItsRecord is extended with that case and fails on 434fa934 (Received unexpected error … a local rebuild over a release is what CODEFLY_AGENT_SOURCE=local asks for), passes here. The missing-sidecar policy under remote resolution is unchanged; the CI finding above still stands for the owner's call.

… it with

The cache is keyed by name alone — <name>__<version> — and nothing compared
its bytes with the release that name stands for. So a build left under a
released version's name, by a developer or by a CI run that installed what it
built, was run by every process resolving that version, silently and with
nothing in any output to tell it from the release (parse__0.1.1 tonight was a
hash-different local build).

v0.19.8 (#819) already holds the downloaded archive to the release's digest and
exports GitHubReleaseToken as the one credential rule. This completes it on the
install side: Download records the installed executable's SHA-256 in a
dot-prefixed sidecar beside it, invisible to every cache scan, and Load holds a
cached executable to that record. A changed one is refused by name and both
digests; one with no record is replaced by the release under remote resolution,
or admitted under CODEFLY_AGENT_SOURCE=local, which is how a local build is run
deliberately. InstalledReleaseDigest exposes the record for the CLI's version
listing and publish verification.

Consequence: a pre-existing cache entry has no record, so the first load after
this upgrade re-downloads each release-resolved agent once.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@antoinetoussaint-byte
antoinetoussaint-byte marked this pull request as draft October 27, 2026 23:17
…Y_AGENT_SOURCE=local, record or not

The digest-mismatch refusal ran before the local-source choice, so an agent
rebuilt locally over a released name — the sidecar still describing the
release's bytes — was refused even with --local-agents, which is the one way a
developer runs a local build deliberately. The local choice now comes first:
the build is admitted and the stale record removed, so a later remote load
sees an entry with no record and fetches the release rather than trusting a
record that no longer describes the file. The remote-resolution refusal now
also names the local escape.

Review finding on the previous head; the extended test fails there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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