Skip to content

fix(canary): make cross-environment cache proofs environment-invariant - #95

Merged
kyle-sexton merged 1 commit into
mainfrom
fix/canary-cache-env-invariant
Jul 14, 2026
Merged

fix(canary): make cross-environment cache proofs environment-invariant#95
kyle-sexton merged 1 commit into
mainfrom
fix/canary-cache-env-invariant

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

What

The canary contract's hosted↔self-hosted cache exchange used ${{ runner.temp }} paths. A cache entry's version is sha256(pathInput | compressionMethod | salt) (actions/cache dist at the pinned SHA), so the exchange can never hit across environments:

  • Path: hosted resolves runner.temp to /home/runner/work/_temp; the fleet worker resolves differently.
  • Compression: hosted has zstd; the fleet image is gzip-only (verified empirically in ghcr.io/melodic-software/ci-runner@be222b0e).

Live proof (first-ever governed dispatch, run 29371617183): the hosted save produced cache version fec98c1e…, reproduced byte-exactly as sha256('/home/runner/work/_temp/hosted-to-self-cache|zstd-without-long|1.0'); the self-hosted restore missed with fail-on-cache-miss.

Fix

Workspace-relative path (.canary-cache/…) in all four cache steps + payload create/verify steps — identical version component in every environment. The compression half of the fix (zstd in the worker image) ships separately in melodic-software/ci-runner; both are required before the laptop gate can pass.

Contract tests green (node --test local-runner-canary.test.cjs), actionlint clean.

Part of melodic-software/github-iac#88.

🤖 Generated with Claude Code

https://claude.ai/code/session_01D5wqdo4adEZmWgjpY9ZjVx

The hosted-to-self and self-to-hosted cache steps keyed their payloads
under ${{ runner.temp }}, but a cache entry's version is a hash of the
path input plus the compression tool, so the hosted save
(/home/runner/work/_temp/..., zstd) could never be restored on a fleet
worker (different temp path, gzip-only image). Proven live on the first
laptop acceptance dispatch: run 29371617183 saved version fec98c1e...,
reproduced exactly as sha256('/home/runner/work/_temp/hosted-to-self-
cache|zstd-without-long|1.0'), and the parity job missed with
fail-on-cache-miss.

Use a workspace-relative path in all four cache steps; the worker-image
side (zstd parity) ships separately in melodic-software/ci-runner.

Part of melodic-software/github-iac#88.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D5wqdo4adEZmWgjpY9ZjVx
@claude

claude Bot commented Jul 14, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 56s —— View job


Code Review

  • Read PR diff and full workflow file
  • Analyze correctness of path fix and cache version logic
  • Check CLAUDE.md compliance (SHA pins, security rules)
  • Post review findings

Summary

The root cause diagnosis and fix are both correct. The change is minimal and well-scoped.


Correctness

Path fix — correct. actions/cache computes the entry version as sha256(resolvedPath | compressionMethod | salt). The resolved path for a relative path: input is $GITHUB_WORKSPACE/<path>. Since both GitHub-hosted and self-hosted containers register their workspace under the same runner-default layout (/home/runner/work/ci-runner-canary/ci-runner-canary), .canary-cache/hosted-to-self expands to the same absolute string on both sides — where ${{ runner.temp }} did not. The live proof in run 29371617183 confirms the previous version mismatch.

All 8 path references updated consistently. The four create/save pairs and four restore/verify pairs across hosted-baseline, local-parity, and hosted-verify are all updated with matching paths. No dangling runner.temp references remain in the cache-path inputs.

Remaining runner.temp uses are intentional. Lines 227, 255, 480, 484, 494, 503, 525, 570, 575, 580 still use runner.temp for local artifact temp paths (probe script output, artifact download destinations). These are single-job-local files, not cache path inputs, so they are not subject to the cross-environment version hash and do not need to change.

fail-on-cache-miss: true retained on both restore steps — the right behavior for a canary proof.


One dependency to land together

The PR body correctly notes the compression fix (zstd in ghcr.io/melodic-software/ci-runner) ships separately, and both are required before the cache exchange passes end-to-end. The inline comment at hosted-baseline L232–235 alludes to this but doesn't call out the external dependency explicitly. Consider noting in the comment (or in a # TODO marker) that zstd in the worker image is gated on the companion ci-runner PR, so a future reader knows the proof can't pass until both land. Minor nit — the PR description covers it fully.


CLAUDE.md compliance

All uses: references remain SHA-pinned with # vX.Y.Z trailing comments. No security ground rules are affected. No pull_request_target / workflow_run trigger present.


Verdict

Looks good to merge. The fix is correct, complete for its stated scope, and the companion compression change in ci-runner is the remaining gate. No blocking issues.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0c31091b4e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/local-runner-canary.yml
@kyle-sexton
kyle-sexton merged commit 4083c8d into main Jul 14, 2026
34 checks passed
@kyle-sexton
kyle-sexton deleted the fix/canary-cache-env-invariant branch July 14, 2026 22:18
kyle-sexton added a commit to melodic-software/ci-runner that referenced this pull request Jul 14, 2026
## What

Adds `zstd` to the worker image's compatibility layer.

## Why

`actions/cache` versions every entry as `sha256(pathInput |
compressionMethod | salt)` and selects zstd whenever the binary is
present. GitHub-hosted runners ship zstd; this image was gzip-only
(verified: `docker run --entrypoint bash
ghcr.io/melodic-software/ci-runner@be222b0e -c 'command -v zstd'` →
absent). Result: hosted↔self-hosted cache exchange can never hit, which
failed the first-ever governed laptop acceptance dispatch
([ci-runner-canary run
29371617183](https://github.com/melodic-software/ci-runner-canary/actions/runs/29371617183))
at the `fail-on-cache-miss` gate.

Path-side fix (workspace-relative cache paths in the canary contract) is
melodic-software/ci-workflows#95; both are required for the
melodic-software/github-iac#88 laptop gate.

This is also the first instance of the fleet-image tool-parity class
tracked for melodic-software/github-iac#89 (pipx precedent) — the
broader parity inventory stays in that issue; this PR ships only what
the acceptance gate needs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01D5wqdo4adEZmWgjpY9ZjVx

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 14, 2026
…contract

Lockstep repin: #95 made the cross-environment cache proofs
environment-invariant and #92 hardened the reusable execution, so the
canonical callers pin the merged commit 9eb22ac carrying both.

Part of melodic-software/github-iac#88.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D5wqdo4adEZmWgjpY9ZjVx
kyle-sexton added a commit that referenced this pull request Jul 14, 2026
…97)

Lockstep repin after #95: template callers now pin the merged
cache-invariant contract commit
`4083c8d82bc4e7a79a9866544101bf8a5261fb50`. The seeded
`ci-runner-canary` repo gets the matching repin via bootstrap re-run
once this merges.

Part of melodic-software/github-iac#88.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01D5wqdo4adEZmWgjpY9ZjVx

Co-authored-by: Claude Fable 5 <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