Skip to content

Fix: give actions/setup-go a cache-dependency-path in the cross-language workflow - #393

Open
AmaadMartin wants to merge 1 commit into
mainfrom
fix/cross-language-workflow-go-cache
Open

Fix: give actions/setup-go a cache-dependency-path in the cross-language workflow#393
AmaadMartin wants to merge 1 commit into
mainfrom
fix/cross-language-workflow-go-cache

Conversation

@AmaadMartin

@AmaadMartin AmaadMartin commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Please ensure you have read the contribution guide before creating a pull request.

Link to Issue or Description of Change

  1. Link to an existing issue (if applicable):
    N/A — no existing issue.
  2. Or, if no issue exists, describe the change:
    Problem: Every run of .github/workflows/cross-language-integration.yml emits a warning annotation and then re-downloads the entire Go module graph.

actions/setup-go@v5 declares cache with default: true, so caching is already on. But with no cache-dependency-path, restoreCache() falls back to findDependencyFile(), which does a readdirSync(GITHUB_WORKSPACE) and looks for a literal go.sum at the repository root. This repo has no root Go module — git ls-files returns exactly two tracked Go manifests and no go.sum anywhere:

  • tests/cross_language/a2a/go_ts/go_client/go.mod
  • tests/cross_language/a2a/ts_go/go_backend/go.mod

(.gitignore line 11 is a bare, unanchored go.sum, so no go.sum is ever committed.)

So the lookup throws, main.ts catches it and downgrades it to a warning, and the job proceeds with no cache:

##[warning]Restore cache failed: Dependencies file is not found in /Users/runner/work/adk-js/adk-js. Supported file pattern: go.sum

Because restoreCache() throws before core.saveState(State.CachePrimaryKey, ...), no primary key is recorded, so the post-job save is a no-op too — Post Setup Go prints Primary key was not generated. Go caching in this workflow is currently inert in both directions: nothing is restored and nothing is saved, so Install Go dependencies does a cold go mod tidy in both fixture directories on every run.

Solution: Give the step the two dependency files to key the cache on. +3 lines, one file, one step:

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.25'
          cache-dependency-path: |
            tests/cross_language/a2a/go_ts/go_client/go.mod
            tests/cross_language/a2a/ts_go/go_backend/go.mod

Why go.mod works as a key source even though the input is documented as go.sum. When cache-dependency-path is set, actions/setup-go skips the root-go.sum probe entirely and hashes the given paths directly. From actions/setup-go@v5 src/cache-restore.ts:

const dependencyFilePath = cacheDependencyPath
  ? cacheDependencyPath
  : findDependencyFile(packageManagerInfo);
const fileHash = await glob.hashFiles(dependencyFilePath);

findDependencyFile() — the only thing that has a go.sum filename policy — is never called. @actions/glob's hashFiles has no filename policy at all: it globs, skips anything outside GITHUB_WORKSPACE, skips directories, and SHA-256s the rest. Patterns are newline-separated, hence the | block scalar.

Why two literal paths instead of a tests/cross_language/**/go.mod glob. The Install Go dependencies step five lines below hardcodes the same two directories. Keeping both lists literal and adjacent makes the coupling visible: adding a third fixture module already requires editing that step, and now it requires editing this one too, in the same review.

Deliberately not done (each is a separate change):

  • No cache: true — it is already the default.
  • The Use Node.js step is left byte-identical.
  • go-version: '1.25' stays as-is; pinning it or switching to go-version-file is out of scope.
  • Un-.gitignoreing go.sum for the two fixtures would give a stronger, checksum-verified key and a supply-chain-verifiable dependency set — but .gitignore line 11 is an unanchored go.sum covering the whole tree, so that is a maintainer policy call, deliberately left out.

Collision check. Three open PRs on this fork touch the same file; none touches the Setup Go step, and none adds cache-dependency-path:

PR Hunk Overlap
#306 chore: cache npm downloads in the cross-language workflow @@ -16,6 +16,8 @@ — adds cache: npm to Use Node.js None
#133 feat/node-version-source-of-truth @@ -16,6 +16,8 @@ — adds node-version-file: .nvmrc to Use Node.js None
#338 Fix: install CI dependencies with npm ci @@ -23,7 +23,7 @@ — changes npm installnpm ci on file line 26 None

This change inserts after file line 23 (go-version: '1.25'). #306 and #133 change line 18; #338 changes line 26. No changed line range overlaps or abuts, so a three-way merge with any of them is clean. (#306 and #133 conflict with each other — identical insertion point — which is not this change's problem and is not resolved here.)

Testing Plan

Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
Unit Tests:
[ ] I have added or updated unit tests for my change.
[x] All unit tests pass locally.

No unit test was added, deliberately. This change adds zero executable lines. The vitest coverage include list in vitest.config.ts is core/src/**/*.ts, dev/src/**/*.ts, integrations/src/**/*.ts — a .github/workflows/*.yml file is not in it and cannot be. The repo has no workflow-schema test and no actionlint step, and nothing anywhere reads .github/workflows (grep across *.ts|*.js|*.json|*.sh returns nothing). A test that parses this YAML and asserts on the string cache-dependency-path would assert the diff back to itself, prove nothing about whether actions/setup-go caches anything, and add a file the maintainers carry forever. The evidence below is strictly stronger.

Local verification against the real implementation (not a re-implementation): the two patterns were run through @actions/glob 0.7.0's hashFiles — the exact function cache-restore.ts calls — with GITHUB_WORKSPACE set to the repo root, reading the patterns out of the committed workflow file rather than retyping them:

patterns from workflow: "tests/cross_language/a2a/go_ts/go_client/go.mod\ntests/cross_language/a2a/ts_go/go_backend/go.mod\n"
::debug::Found 2 files to hash.
fileHash: 4439a2d96dba9207fb8cb78986153e0e1d2a8cc3af453a8a9fc7a91cee83b98f

A non-empty hash is exactly the condition cache-restore.ts requires (if (!fileHash) throw new Error('Some specified paths were not resolved, ...')).

Negative controls — proof the check can fail. Two mutations were run through the same hashFiles call:

  1. The pre-fix state. Hashing go.sum (what findDependencyFile() probes for at the root) → ::debug::No matches found for glob, empty hash. This is the root cause: readdirSync(workspace).includes('go.sum') is false, so findDependencyFile() throws Dependencies file is not found in ... Supported file pattern: go.sum — verbatim the warning in the run logs.
  2. A one-character typo in a path (go.mo instead of go.mod) → ::debug::No matches found for glob, empty hash → Some specified paths were not resolved, unable to cache dependencies. The paths are load-bearing, not decorative; a misspelling reintroduces the failure under a different message.

Also run locally: python3 -c "import yaml; yaml.safe_load(open('.github/workflows/cross-language-integration.yml'))" parses, and secretlint (the only repo hook that sees .yml, via the "*" lint-staged entry and npx secretlint "**/*" in validation.yaml) exits 0 on the changed file. npm run format/lint are prettier "**/*.ts" and ESLint on {js,ts}, so neither sees this file.

Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.

The workflow run is the integration test. To verify:

  1. Open the Cross-Language Tests run for this PR and expand Setup Go. There must be no line starting with Restore cache failed:, and it must print Cache is not found (a miss — proving a key was computed and a lookup actually happened) or Cache restored from key: setup-go-….
  2. Expand Post Setup Go. It must print Cache saved with the key: setup-go-macOS-arm64-go-… and must not print Primary key was not generated.
  3. Re-run the same commit (gh run rerun <run-id>) and re-check: Setup Go should now print Cache restored from key: … with the same key, Post Setup Go should print Cache hit occurred on the primary key …, not saving cache., and the go: downloading count in Install Go dependencies should drop.
gh run view --job <job-id> --log | grep -nE 'Restore cache failed|Cache is not found|Cache restored from key|Cache saved with the key|Cache hit occurred|Primary key was not generated'
gh run view --job <job-id> --log | grep -c 'go: downloading'

CI evidence (all four runs below are real, on this fork)

Baseline (pre-change). Run 30638145428, job 91180931362, 2026-07-31T14:19:59Z — the Cross-Language run for PR #392, 17 minutes before mine. That branch's diff is two TypeScript files (core/src/models/interactions_utils.ts, core/test/models/interactions_utils_test.ts), so its copy of this workflow is byte-identical to main. Same workflow, same macos-latest runner class, no cache-dependency-path. It reproduces the bug verbatim:

Setup Go       ##[warning]Restore cache failed: Dependencies file is not found in /Users/runner/work/adk-js/adk-js. Supported file pattern: go.sum
Post Setup Go  Primary key was not generated. Please check the log messages above for more errors or information
$ grep -c 'go: downloading'   ->  53

Run #1 — cold key. Run 30639356372 attempt 1, job 91185030271. All four required assertions hold:

Setup Go       [command].../go env GOMODCACHE
Setup Go       [command].../go env GOCACHE
Setup Go       Cache is not found                       <- a miss, not a failure: a key WAS computed
Post Setup Go  Cache saved with the key: setup-go-macOS-arm64-go-1.25.12-4439a2d96dba9207fb8cb78986153e0e1d2a8cc3af453a8a9fc7a91cee83b98f

No line matching Restore cache failed:; no Primary key was not generated.; job conclusion success. The cached directories in the log are /Users/runner/go/pkg/mod (GOMODCACHE) and /Users/runner/Library/Caches/go-build (GOCACHE) — the build cache is cached too, not just modules.

Note the hash in that key — 4439a2d96dba9207fb8cb78986153e0e1d2a8cc3af453a8a9fc7a91cee83b98f — is byte-identical to the hash computed locally by @actions/glob before pushing (quoted in the Testing Plan above). The local verification reproduced CI's key exactly.

Runs #2 and #3 — warm key. Same commit re-run twice (gh run rerun 30639356372), jobs 91185574669 and 91186063882. Both identical:

Setup Go       Cache restored from key: setup-go-macOS-arm64-go-1.25.12-4439a2d9...   <- same key as run #1
Post Setup Go  Cache hit occurred on the primary key setup-go-macOS-arm64-go-1.25.12-4439a2d9..., not saving cache.
$ grep -c 'go: downloading'   ->  0        (down from 53)

Annotation diff — the headline fix

Job-level annotations, straight from GET /check-runs/<id>/annotations:

Baseline (job 91180931362) This PR (job 91186063882)
Restore cache failed: Dependencies file is not found in … present gone
Node.js 20 is deprecated … actions/setup-go@v5 present present

The Node-20 deprecation notice is pre-existing on the baseline and is unrelated to this change (it is about the action's runtime, not its inputs); removing it would mean bumping actions/setup-go, which is a different PR. This change removes exactly one standing warning, permanently, from every future run.

Timings — honest measurement

Seconds, from GET /actions/runs/<id>/jobs. One baseline sample, one cold sample, two warm samples.

Step Baseline (pre-change) Run #1 cold Run #2 warm Run #3 warm
Setup Go 2 3 8 8
Install dependencies (npm) 19 20 20 19
Install Go dependencies 15 14 1 0
Build packages 10 13 9 11
Run cross-language integration tests 43 40 11 12
Post Setup Go 1 10 1 1
Total job 101 110 61 61
go: downloading count 53 53 0 0

The cold run is slower, as expected: 110 s vs 101 s (+9 s). Post Setup Go goes from 1 s to 10 s because it now actually compresses and uploads both cache directories instead of silently no-op'ing. That cost is paid once per (Go patch version × go.mod content) key.

The warm runs are 61 s vs a 101 s baseline (−40 s, −40%), reproduced identically twice. Where that comes from, per step:

  • Install Go dependencies 15 s → 0–1 s (−14 s): go mod tidy is served from the restored GOMODCACHE.
  • Run cross-language integration tests 43 s → 11–12 s (−31 s): this is the larger term. The fixtures shell out to go run ., which compiles the module graph; the restored GOCACHE (Go build cache, not just the module cache) serves that compilation.
  • Setup Go 2 s → 8 s (+6 s): the restore itself costs time.
  • npm install and Build packages move within noise (19–20 s and 9–13 s).

Two caveats I want to state rather than round away:

  1. This is a bigger win than a module-download analysis predicts. Looking only at Install Go dependencies (15 s of a 101 s job) caps the win at ~15%. The measured 40% comes mostly from GOCACHE accelerating the test step, which is easy to overlook because that step is not named after Go. I would not have believed this without the per-step numbers.
  2. Sample sizes are small (n=1 baseline, n=1 cold, n=2 warm) and hosted-runner timings are noisy. The two warm samples landing on 61 s each is reassuring, but treat the −40 s as an indication, not a benchmark. The claims I would defend without any timing data at all are the two structural ones: a standing warning annotation is removed from every run of this workflow and the post step stops silently no-op'ing, and 53 module fetches from proxy.golang.org per run are avoided — removing a per-run dependency on an external service and one network-flake surface.

No extrapolation to other workflows, other runners, or "CI minutes saved" is offered; there is no data for that here.

One unrelated CI flake, disclosed

The first attempt at run-tests (windows-latest) (the validation.yaml matrix, not the workflow this PR edits) failed with Error: Test timed out in 5000ms. at core/test/code_executors/unsafe_local_code_executor_test.ts:145 — 1 failed, 2674 passed. That test spawns a real Python interpreter and passes no vitest timeout, so it inherits the 5000 ms default; Windows process creation exceeds that on a cold runner.

It is not caused by this change, and I verified that rather than asserting it: re-running the identical commit passed (job 91187171827). This PR's whole diff is three lines in cross-language-integration.yml, a workflow that runs only on macos-latest and that validation.yaml never reads. I have left the flake alone rather than bundling an unrelated fix into this PR; it is filed as separate follow-up work.

All checks are green on the final commit: run-tests (cross-language) pass, run-tests (macos-latest / ubuntu-latest / windows-latest) pass, check-license pass.

Checklist

[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas.
[x] I have added tests that prove my fix is effective or that my feature works.
[x] New and existing unit tests pass locally with my changes.

actions/setup-go@v5 enables caching by default, but with no
cache-dependency-path it probes for a literal go.sum at the repository
root. adk-js has no root Go module (and .gitignore ignores go.sum
tree-wide), so findDependencyFile() throws, main.ts downgrades it to
"Restore cache failed: Dependencies file is not found in ...", and no
primary key is ever recorded -- so the post-job save no-ops too.

Supplying cache-dependency-path skips that probe entirely: the value goes
straight to @actions/glob's hashFiles, which has no filename policy, so
the two tracked fixture go.mod files are a valid key source.

Listing the two paths literally (rather than a glob) keeps them adjacent
to the Install Go dependencies step, which hardcodes the same two
directories.
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