Skip to content

feat(setup): add Copilot CLI toolcache resolver with scoped release-mode opt-in#34918

Open
salmanmkc wants to merge 14 commits into
mainfrom
salmanmkc/setup-copilot-resolver
Open

feat(setup): add Copilot CLI toolcache resolver with scoped release-mode opt-in#34918
salmanmkc wants to merge 14 commits into
mainfrom
salmanmkc/setup-copilot-resolver

Conversation

@salmanmkc
Copy link
Copy Markdown
Collaborator

@salmanmkc salmanmkc commented May 26, 2026

Summary

Adds a Copilot CLI toolcache resolver to actions/setup so eligible Copilot jobs can pick up a compatible CLI baked into the runner image instead of running npm install -g @github/copilot at job start.

Why

Runtime npm install -g @github/copilot adds ~10s to workflow runs and depends on npm registry availability. Hosted runners now bake @github/copilot into toolcache ($RUNNER_TOOL_CACHE/copilot-cli/<version>/<arch>/). This change enables safe cached resolution while preserving existing fallback behavior.

How it works

For Copilot engine workflows:

  1. The compiler emits setup-step env vars only for jobs that actually install/run Copilot CLI (agent + threat-detection), and only in release/action mode:
    • INPUT_INSTALL_COPILOT: 'true'
    • INPUT_GH_AW_VERSION=<compiler version>
  2. setup.sh checks INPUT_INSTALL_COPILOT=true and then invokes actions/setup/js/install_copilot_cli.cjs (guarded with command -v node and non-fatal behavior).
  3. The resolver:
    • Fetches compat matrix from gh-aw-actions/.github/aw/compat.json (5s timeout via AbortSignal.timeout).
    • Falls back to bundled actions/setup/compat.json on error.
    • Selects compatible cached Copilot CLI from toolcache.
    • On hit: appends <dir>/bin to $GITHUB_PATH, sets copilot-cached=true and copilot-path=<dir>.
    • On miss/error: sets copilot-cached=false and exits 0.
  4. The compiler-emitted installer step is gated with if: steps.setup.outputs.copilot-cached != 'true', so it skips on cache hit and runs on miss.

Diff shape

  • actions/setup/action.yml — adds copilot-cached, copilot-path outputs.
  • actions/setup/setup.sh — runs resolver only when explicitly opted in (INPUT_INSTALL_COPILOT=true).
  • actions/setup/compat.json (new) — fallback matrix.
  • actions/setup/js/install_copilot_cli.cjs (new) — resolver using native fetch.
  • actions/setup/js/install_copilot_cli.test.cjs (new) — resolver unit tests.
  • pkg/workflow/copilot_engine_installation.go — gates installer step on setup output.
  • pkg/workflow/compiler_yaml_step_generation.go — emits Copilot resolver env only for opted-in jobs and only in release/action mode.
  • pkg/workflow/setup_step_version_test.go — adds mode-sensitive coverage for env emission.
  • Updated affected lock/golden outputs accordingly (including removing these env vars from dev-mode outputs).

Backward compatibility

  • Non-Copilot workflows: unchanged.
  • Copilot workflows without compatible cache: installer runs as before.
  • Runners without Node: resolver is skipped, installer runs as before.
  • Dev/script-mode generated outputs no longer receive Copilot resolver env injection.

Tests

  • make lint
  • go test ./pkg/workflow -count=1
  • make test
  • make update-wasm-golden (to refresh affected fixtures)
  • JS resolver tests remain green (install_copilot_cli.test.cjs).

Adds an opt-in path that skips the runtime `npm install -g @github/copilot`
when a compatible build is already present in the runner tool cache.

Behavior changes:
- New `setup-copilot-resolver` feature flag (default off). When enabled in a
  workflow's frontmatter, the compiler emits `INPUT_INSTALL_COPILOT: 'true'`
  on the setup step and adds `if: steps.setup.outputs.copilot-cached != 'true'`
  to the compiler-emitted install step. With the flag off, the compiler emits
  identical YAML to before (verified: golden fixtures unchanged).
- New `actions/setup/js/install_copilot_cli.cjs` resolver runs from setup.sh
  when `INPUT_INSTALL_COPILOT=true`. On a hit, it appends the toolcache bin
  dir to $GITHUB_PATH and writes `copilot-cached=true`. On any miss or error
  (no toolcache entry, version out of range, network failure, malformed
  matrix, etc.) it writes `copilot-cached=false` and exits 0 so the existing
  bash installer runs as before.
- Resolver has zero npm dependencies (uses only fs/path/https) so it cannot
  introduce a new install step itself.
- Two new step outputs on the setup action: `copilot-cached` and
  `copilot-path`.

Resolution logic:
- Fetches compat matrix from gh-aw-actions main, falls back to bundled
  `actions/setup/compat.json` on any error (5s timeout).
- Picks the first matrix row whose `max-gh-aw` covers the current compiler
  version, then selects the highest cached version in
  [min-agent, max-agent].
- Toolcache layout matches runner-images convention:
  $RUNNER_TOOL_CACHE/copilot-cli/<version>/<arch>/{bin/copilot, ..}.complete

Tests: 23 vitest cases for the resolver covering semver parsing/comparison,
matrix row matching, range selection, arch detection, and toolcache scanning
(hit, miss, missing marker, missing binary, non-semver dir, empty cache).
All existing Go workflow tests pass unchanged.
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

};
let req;
try {
req = https.get(COMPAT_URL, { timeout: FETCH_TIMEOUT_MS }, res => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Use fetch

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in 3026304 — swapped https.get + buffer for fetch with AbortSignal.timeout(5000). Matches check_version_updates.cjs / send_otlp_span.cjs. Same contract (returns parsed JSON or null on any error, never throws); 23/23 unit tests still pass.

@pelikhan
Copy link
Copy Markdown
Collaborator

Just have it always on. It is more complicated to add a feature flag for this than anything.

Node 24 ships fetch globally, so the manual https.get + chunk buffer
+ timeout wiring isn't needed. Replace it with fetch + AbortSignal.timeout,
matching the convention in check_version_updates.cjs and send_otlp_span.cjs.

Same contract: returns parsed JSON on 2xx + valid JSON, returns null on
any error (network, timeout, non-200, parse failure). Never throws.
@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor

Hey @salmanmkc 👋 — thanks for putting together this toolcache resolver for the Copilot CLI setup step! The idea of skipping the npm install -g @github/copilot`` step when a compatible build is already baked into the runner image is a solid performance win, and the implementation is clearly well thought-out.

However, this repository has a specific contribution process for community members that this PR doesn't follow:

  • PRs from non-core-team members are not accepted directly. The core team (@dsyme, @eaftan, @pelikhan, @krzysztof-cieslak) uses agentic development to implement all changes. Community members are asked to open a detailed issue with an agentic plan describing what they want built — a core team member will then pick it up and implement it through an agent.

To get this idea into the codebase, please close this PR and open a GitHub Issue instead, describing the feature in detail (the why, the how, the constraints). Your write-up here is already excellent and would make a great agentic plan!

If you'd like a head start on drafting that issue, you can use this prompt with your AI assistant:

Create a detailed GitHub issue (agentic plan) for the gh-aw repository proposing an opt-in toolcache resolver for the Copilot CLI setup step.

The plan should cover:
1. Problem: `npm install -g `@github/copilot`` adds ~10s per workflow run and requires npm registry access. Hosted runners now bake the CLI into the tool cache.
2. Proposed solution: A new `setup-copilot-resolver` feature flag (default off). When enabled, the compiler emits extra env vars on the setup step; setup.sh invokes a zero-dependency Node.js CJS resolver that checks `$RUNNER_TOOL_CACHE/copilot-cli/<version>/<arch>/` against a compat matrix (fetched live with local fallback). On a cache hit the install step is gated with `if: steps.setup.outputs.copilot-cached != 'true'`; on any miss/error it falls back silently.
3. Files expected to change: actions/setup/action.yml, actions/setup/setup.sh, a new actions/setup/js/install_copilot_cli.cjs resolver, actions/setup/compat.json fallback matrix, pkg/constants/feature_constants.go, pkg/workflow/copilot_engine_installation.go, pkg/workflow/compiler_yaml_step_generation.go, and related compiler job builders.
4. Testing: unit tests for the resolver (happy path, cache miss, network error, version boundary) and Go tests for the feature-flag gating logic.
5. Rollout: flag off by default; golden fixtures must remain unchanged when flag is off.

Generated by ✅ Contribution Check · sonnet46 2.6M ·

Copy link
Copy Markdown
Collaborator

@pelikhan pelikhan left a comment

Choose a reason for hiding this comment

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

Add type annotations and typecheck

const FETCH_TIMEOUT_MS = 5000;

function log(msg) {
console.log(`[install_copilot_cli] ${msg}`);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You can require shim.cjs and use core.debug/info... for logging. Makes things more consistent for agents.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in 8a55a3a — require shim.cjs at the top, swapped console.log/console.error for core.info/core.warning. Same [install_copilot_cli] prefix preserved for grep-ability.

The setup-copilot-resolver feature flag added a layer of plumbing more
complex than the value it provided. Drop the flag, the boolean parameter
on generateSetupStep, and the dedicated INPUT_INSTALL_COPILOT env. The
compiler now emits INPUT_GH_AW_VERSION for any Copilot-engine workflow,
which setup.sh treats as the signal to invoke the resolver. The bash
installer step gate (skip on copilot-cached=true) is now always applied
for Copilot workflows, which is a no-op when the resolver reports a miss.

Non-Copilot workflows are unchanged: no env var emitted, no resolver run,
no golden diff.
@salmanmkc
Copy link
Copy Markdown
Collaborator Author

Done — dropped the feature flag entirely in fb6b0ef. The resolver now runs unconditionally for any Copilot-engine workflow (signal is the compiler-emitted INPUT_GH_AW_VERSION env). Non-Copilot workflows are unchanged (no env var, no resolver invocation, no golden diff). PR description updated to reflect the new shape.

@github-actions

This comment has been minimized.

@pelikhan
Copy link
Copy Markdown
Collaborator

Actually... my bad this feature is only needed to run in agent/detection jobs + copilot engine selected.

- Require shim.cjs for global core/context so logging routes through
  core.info / core.warning instead of console.log / console.error,
  matching the convention used across the other setup/js modules.
- Add // @ts-check directive (now passes tsc --noEmit with the existing
  strictNullChecks config) and JSDoc type annotations on every exported
  function plus the ParsedSemver and CompatRow shared typedefs.
- Tighten error narrowing in catch blocks (Error instance guards) and
  add explicit casts where the matrix payload is treated as unknown.

Resolver tests (23/23) and full typecheck still pass.
@github-actions

This comment has been minimized.

@salmanmkc
Copy link
Copy Markdown
Collaborator Author

Type annotations + typecheck addressed in 8a55a3a — added // @ts-check directive, JSDoc on every exported function, shared ParsedSemver and CompatRow typedefs, narrowed catch unknown to Error before reading .message. npm run typecheck is clean (the file was already in the tsconfig include glob; this just makes the contract explicit at the file level).

@github-actions

This comment has been minimized.

@salmanmkc
Copy link
Copy Markdown
Collaborator Author

On it — bringing the flag back as compiler-emitted INPUT_INSTALL_COPILOT: 'true' on the setup step, gated in setup.sh on that env. Compiler only emits it for jobs that actually run Copilot CLI (the main agent job and the threat-detection job, when the Copilot engine is selected). Other consumers of the setup action (cache, activation, pre_activation, safe-outputs, unlock, publish-assets, repo-memory, notify-comment, experiments) will not trigger the resolver. Pushing in a moment.

Re-introduce INPUT_INSTALL_COPILOT as the explicit opt-in for the
toolcache resolver in actions/setup/setup.sh. The compiler emits this
flag (alongside INPUT_GH_AW_VERSION) on the setup step env block only
for jobs that actually invoke the Copilot CLI: the main agent job and
the threat-detection job.

Other jobs that share the setup action (activation, pre-activation,
cache, unlock, safe-outputs, notify-comment, publish-assets,
repo-memory, experiments) opt out, so the resolver stays a no-op there.

Adds TestGenerateSetupStepEmitsInstallCopilotGate covering the three
cases: copilot engine + opt-in emits both env vars; copilot engine
without opt-in suppresses them; non-copilot engine ignores the opt-in.
@salmanmkc
Copy link
Copy Markdown
Collaborator Author

Pushed c806598:

  • Added installCopilot bool parameter to generateSetupStep; only compiler_main_job.go (agent) and threat_detection.go (detection) pass true. The other 9 callers pass false.
  • When installCopilot && setupEngineID == "copilot", the compiler emits both INPUT_INSTALL_COPILOT: 'true' and INPUT_GH_AW_VERSION: "..." on the setup step env block (script + dev/release modes).
  • setup.sh resolver gate restored to [ "${INPUT_INSTALL_COPILOT:-false}" = "true" ].
  • Goldens regenerated: both env vars now appear only on agent jobs.
  • Added TestGenerateSetupStepEmitsInstallCopilotGate (3 cases: copilot+opt-in, copilot+no-opt-in, non-copilot+opt-in). Full pkg/workflow test suite passes (260s).

Verified across 6 parallel review passes — no blockers; one acknowledged note that custom-command Copilot workflows (workflows that set engine.command:) will still receive the resolver opt-in but the resolver is a harmless no-op in that path since the custom script doesn't invoke the copilot binary. Happy to tighten further if you want.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot mentioned this pull request May 27, 2026
Resolves conflicts in compiler_yaml_step_generation.go where main
extracted generateOTLPOIDCMintStep helper and introduced a setupLines
slice. Kept the 7-param generateSetupStep signature (installCopilot
bool) and routed INPUT_INSTALL_COPILOT / INPUT_GH_AW_VERSION emission
through setupLines so they nest under the setup step env block.

Resolves test conflict in setup_step_version_test.go by keeping both
TestGenerateSetupStepEmitsInstallCopilotGate and the three new OTLP
mint tests, updating the OTLP tests to use the 7-param signature.

Regenerated workflow lock files via make recompile to refresh fixtures
for TestHashConsistencyAcrossLockFiles.
@github-actions

This comment has been minimized.

@pelikhan
Copy link
Copy Markdown
Collaborator

Did you consider updating the script that installs copilot instead?

@github-actions

This comment has been minimized.

@salmanmkc salmanmkc marked this pull request as ready for review May 28, 2026 14:50
Copilot AI review requested due to automatic review settings May 28, 2026 14:50
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

🧪 Test Quality Sentinel completed test quality analysis.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

PR Code Quality Reviewer completed the code quality review.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions
Copy link
Copy Markdown
Contributor

✅ smoke-ci: safeoutputs CLI comment + comment-memory run (26582319885)

Generated by 🧪 Smoke CI for issue #34918 ·

@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 100/100 — Excellent

Analyzed 26 tests (23 JavaScript + 3 Go table rows): 26 design tests, 0 implementation tests, 0 guideline violations.

📊 Metrics & Test Classification (26 tests analyzed)
Metric Value
New/modified tests analyzed 26
✅ Design tests (behavioral contracts) 26 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 26 (100%)
Duplicate test clusters 0
Test inflation detected No
🚨 Coding-guideline violations 0

Test Classification Details

Test File Classification Issues Detected
parseSemver — parses release versions install_copilot_cli.test.cjs ✅ Design None
parseSemver — parses pre-release versions install_copilot_cli.test.cjs ✅ Design None
parseSemver — rejects non-string input install_copilot_cli.test.cjs ✅ Design Edge case covered
parseSemver — rejects malformed strings install_copilot_cli.test.cjs ✅ Design Edge case covered
cmpSemver — compares major/minor/patch install_copilot_cli.test.cjs ✅ Design Multiple orderings
cmpSemver — treats pre-release as lower install_copilot_cli.test.cjs ✅ Design Edge case covered
rowMatchesGhAw — wildcard rows install_copilot_cli.test.cjs ✅ Design Edge case covered
rowMatchesGhAw — version ≤ max-gh-aw install_copilot_cli.test.cjs ✅ Design Boundary value
rowMatchesGhAw — version exceeds max-gh-aw install_copilot_cli.test.cjs ✅ Design Error case
rowMatchesGhAw — unparseable compiler version install_copilot_cli.test.cjs ✅ Design Edge case covered
copilotRows — well-formed matrix install_copilot_cli.test.cjs ✅ Design None
copilotRows — malformed inputs install_copilot_cli.test.cjs ✅ Design Null/empty edge cases
pickRange — first matching row install_copilot_cli.test.cjs ✅ Design Boundary values
pickRange — skips non-matching max-gh-aw install_copilot_cli.test.cjs ✅ Design Version filtering
pickRange — skips unparseable min/max-agent install_copilot_cli.test.cjs ✅ Design Malformed input
pickRange — returns null when no match install_copilot_cli.test.cjs ✅ Design Null/empty case
detectArch — returns current arch string install_copilot_cli.test.cjs ✅ Design None
findCachedCopilot — highest version in range install_copilot_cli.test.cjs ✅ Design Multiple versions
findCachedCopilot — ignores out-of-range versions install_copilot_cli.test.cjs ✅ Design Boundary filtering
findCachedCopilot — ignores missing .complete marker install_copilot_cli.test.cjs ✅ Design Incomplete install
findCachedCopilot — ignores missing binary install_copilot_cli.test.cjs ✅ Design Corrupt toolcache
findCachedCopilot — ignores non-semver dirs install_copilot_cli.test.cjs ✅ Design Malformed dir names
findCachedCopilot — null when dir missing install_copilot_cli.test.cjs ✅ Design Missing dir
TestGenerateSetupStepEmitsInstallCopilotGate — copilot + opt-in emits setup_step_version_test.go ✅ Design Happy path
TestGenerateSetupStepEmitsInstallCopilotGate — copilot without opt-in suppresses setup_step_version_test.go ✅ Design Opt-out behavior
TestGenerateSetupStepEmitsInstallCopilotGate — non-copilot ignores opt-in setup_step_version_test.go ✅ Design Engine-type isolation

Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 3 new table-driven rows in 1 modified test function — unit (//go:build !integration) ✅ build tag present
  • 🟨 JavaScript (*.test.cjs): 23 vitest tests — real filesystem I/O, no business-logic mocking

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). The JavaScript tests use real temporary directories to validate filesystem-scanning logic, covering all significant edge cases (missing .complete marker, missing binary, out-of-range versions, non-semver directory names). The new Go table-driven test directly asserts the conditional emission contract for INPUT_INSTALL_COPILOT and INPUT_GH_AW_VERSION.

📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 1.5M ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 100/100. Test quality is excellent — 0% of new tests are implementation tests (threshold: 30%).

@github-actions
Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR introduces 160 new lines in business-logic directories (over the 100-line default threshold) but does not link an Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/34918-toolcache-resolver-for-copilot-cli.md — please review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff and description.
  2. Complete or correct any sections where the draft says "Not inferable from current PR evidence" or where the AI's reading of the diff is wrong. In particular, double-check:
    • The compat-matrix URL and timeout in the Normative Specification.
    • The list of genuine alternatives — were any of the rejected options actually considered seriously?
    • The negative consequences — is the maintenance and drift cost of the bundled compat.json framed correctly?
  3. Flip Status: DraftStatus: Proposed (or Accepted once team-reviewed) and commit the finalized ADR.
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-34918: Toolcache Resolver for Copilot CLI

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does — including the alternatives you rejected. Future contributors (and your future self) will thank you when they need to understand why actions/setup has a Node-based resolver instead of a composite action or a pinned version.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — what is the problem? What forces are at play?
  • Decision — what did you decide? Why?
  • Alternatives Considered — what else could have been done? (≥2 genuine alternatives, no strawmen)
  • Consequences — what are the trade-offs (positive and negative)?

The draft uses the two-part variant: a human-friendly narrative on top, plus a Normative Specification in RFC 2119 language that translates the decision into testable MUST / SHOULD / MAY requirements. The conformance paragraph at the bottom is what future reviewers will check the implementation against.

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 34918-... for this PR).

References: §26582322159

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · opus47 5M ·

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a feature-flagged Copilot CLI “toolcache resolver” to the actions/setup action and updates the workflow compiler to (a) opt Copilot jobs into the resolver and (b) skip the existing bash-based Copilot CLI installer step when a compatible cached CLI is found on the runner.

Changes:

  • Emit Copilot-only setup env (INPUT_INSTALL_COPILOT, INPUT_GH_AW_VERSION) for jobs that actually execute Copilot (main + threat-detection).
  • Add copilot-cached / copilot-path outputs to the setup action and gate Copilot installer steps on steps.setup.outputs.copilot-cached != 'true'.
  • Update locked workflows and WASM golden fixtures to reflect the new env vars and gated installer step.
Show a summary per file
File Description
actions/setup/action.yml Declares new setup-step outputs for Copilot CLI cache resolution.
actions/setup/compat.json Adds bundled fallback compatibility matrix used by the resolver.
actions/setup/setup.sh Invokes the resolver when opted-in via INPUT_INSTALL_COPILOT=true (non-fatal).
actions/setup/js/install_copilot_cli.cjs Implements toolcache resolver (live compat fetch + bundled fallback + cache scan).
actions/setup/js/install_copilot_cli.test.cjs Adds vitest coverage for resolver selection/scanning behavior.
pkg/workflow/compiler_yaml_step_generation.go Extends generateSetupStep to optionally emit Copilot resolver env vars.
pkg/workflow/copilot_engine_installation.go Gates Copilot installer step(s) on setup output copilot-cached.
pkg/workflow/compiler_main_job.go Opts the main Copilot agent job into the resolver via installCopilot=true.
pkg/workflow/threat_detection.go Opts the threat-detection job into the resolver via installCopilot=true.
pkg/workflow/cache.go Updates generateSetupStep call signature (passes installCopilot=false).
pkg/workflow/compiler_activation_job_builder.go Updates generateSetupStep call signature (passes installCopilot=false).
pkg/workflow/compiler_experiments.go Updates generateSetupStep call signature (passes installCopilot=false).
pkg/workflow/compiler_pre_activation_job.go Updates generateSetupStep call signature (passes installCopilot=false).
pkg/workflow/compiler_safe_outputs_job.go Updates generateSetupStep call signature (passes installCopilot=false).
pkg/workflow/compiler_unlock_job.go Updates generateSetupStep call signature (passes installCopilot=false).
pkg/workflow/notify_comment.go Updates generateSetupStep call signature (passes installCopilot=false).
pkg/workflow/publish_assets.go Updates generateSetupStep call signature (passes installCopilot=false).
pkg/workflow/repo_memory.go Updates generateSetupStep call signature (passes installCopilot=false).
pkg/workflow/testdata/TestWasmGolden_AllEngines/copilot.golden Updates golden output for Copilot: new env vars + gated installer.
pkg/workflow/testdata/TestWasmGolden_CompileFixtures/basic-copilot.golden Updates golden output for Copilot: new env vars + gated installer.
pkg/workflow/testdata/TestWasmGolden_CompileFixtures/playwright-cli-mode.golden Updates golden output for Copilot: new env vars + gated installer.
pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden Updates golden output for Copilot: new env vars + gated installer.
pkg/workflow/testdata/TestWasmGolden_CompileFixtures/with-imports.golden Updates golden output for Copilot: new env vars + gated installer.
.github/workflows/ab-testing-advisor.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/ace-editor.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/agent-performance-analyzer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/agent-persona-explorer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/agentic-token-audit.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/agentic-token-optimizer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/archie.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/architecture-guardian.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/artifacts-summary.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/auto-triage-issues.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/bot-detection.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/brave.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/breaking-change-checker.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/chaos-pr-bundle-fuzzer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/ci-coach.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/cli-consistency-checker.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/code-scanning-fixer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/code-simplifier.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/constraint-solving-potd.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/contribution-check.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/copilot-cli-deep-research.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/copilot-opt.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/copilot-pr-merged-report.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/copilot-pr-nlp-analysis.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/copilot-pr-prompt-analysis.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/craft.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-agent-of-the-day-blog-writer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-architecture-diagram.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-assign-issue-to-user.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-cli-performance.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-cli-tools-tester.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-community-attribution.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-compiler-quality.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-experiment-report.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-file-diet.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-firewall-report.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-geo-optimizer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-hippo-learn.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-issues-report.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-malicious-code-scan.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-mcp-concurrency-analysis.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-model-inventory.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-news.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-performance-summary.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-regulatory.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-repo-chronicle.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-safe-output-integrator.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-secrets-analysis.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-security-observability.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-semgrep-scan.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-sentrux-report.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-skill-optimizer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-spdd-spec-planner.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-syntax-error-quality.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-team-status.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-testify-uber-super-expert.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/daily-workflow-updater.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/dead-code-remover.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/delight.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/dependabot-campaign.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/dependabot-go-checker.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/dependabot-repair.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/dependabot-worker.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/deployment-incident-monitor.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/dev-hawk.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/dictation-prompt.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/discussion-task-miner.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/docs-noob-tester.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/draft-pr-cleanup.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/example-permissions-warning.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/firewall-escape.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/firewall.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/functional-pragmatist.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/github-remote-mcp-auth-test.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/glossary-maintainer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/gpclean.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/hippo-embed.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/issue-monster.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/issue-triage-agent.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/jsweep.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/layout-spec-maintainer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/lint-monster.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/linter-miner.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/mattpocock-skills-reviewer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/mcp-inspector.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/mergefest.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/metrics-collector.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/notion-issue-summary.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/org-health-report.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/otlp-data-quality-validator.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/outcome-collector.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/pdf-summary.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/plan.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/poem-bot.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/pr-code-quality-reviewer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/pr-description-caveman.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/pr-nitpick-reviewer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/pr-sous-chef.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/pr-triage-agent.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/python-data-charts.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/q.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/refactoring-cadence.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/refiner.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/release.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/repo-audit-analyzer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/repo-tree-map.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/repository-quality-improver.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/research.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/security-compliance.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/security-review.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/slide-deck-maintainer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/smoke-ci.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/smoke-copilot-arm.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/smoke-copilot.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/smoke-create-cross-repo-pr.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/smoke-multi-pr.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/smoke-otel-backends.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/smoke-project.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/smoke-service-ports.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/smoke-temporary-id.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/smoke-test-tools.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/smoke-update-cross-repo-pr.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/smoke-workflow-call-with-inputs.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/smoke-workflow-call.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/spec-extractor.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/spec-librarian.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/stale-pr-cleanup.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/stale-repo-identifier.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/sub-issue-closer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/super-linter.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/technical-doc-writer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/terminal-stylist.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/test-dispatcher.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/test-project-url-default.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/test-quality-sentinel.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/test-workflow.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/tidy.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/uk-ai-operational-resilience.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/ubuntu-image-analyzer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/update-astro.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/video-analyzer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/visual-regression-checker.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/weekly-blog-post-writer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/weekly-editors-health-check.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/weekly-issue-summary.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/weekly-safe-outputs-spec-review.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/workflow-generator.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/workflow-health-manager.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/workflow-normalizer.lock.yml Regenerates locked workflow to include resolver env + gated installer.
.github/workflows/workflow-skill-extractor.lock.yml Regenerates locked workflow to include resolver env + gated installer.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 178/178 changed files
  • Comments generated: 0

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Non-blocking observations on 3 issues

Overall design is sound — resolver never exits non-zero, copilot-cached != 'true' gate is correctly wired in both the main agent job and the threat-detection job, and 23 unit tests cover the core resolution paths. Three non-blocking issues worth addressing before or after merge:

📋 Issue summary
  1. gateStepsOnCopilotCached fragile step-shape assumption (pkg/workflow/copilot_engine_installation.go:127) — steps that don't start with - name: silently skip the if: gate; no warning emitted, no unit test directly covers gateStepsOnCopilotCached.

  2. Bundled compat.json ceiling becomes stale (actions/setup/compat.json:9) — max-agent: "1.0.54" is a narrow window; once runner images bake in 1.0.55+, the bundled fallback produces no cache hits. Consider a high ceiling (e.g. "2.0.0") or a release-automation step to keep it in sync.

  3. Network fetch before toolcache existence check (actions/setup/js/install_copilot_cli.cjs:286) — fetchLiveMatrix() runs even when $RUNNER_TOOL_CACHE/copilot-cli/ is empty. On restricted networks this burns the full 5 s timeout for every cache miss. Checking for directory entries first would short-circuit the common no-cache case instantly.

🔎 Code quality review by PR Code Quality Reviewer · sonnet46 3.9M

}
if !strings.HasPrefix(step[0], " - name:") {
out = append(out, step)
continue
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Silent gate bypass when installer step format changes: if step[0] ever stops matching " - name:" (e.g. GenerateCopilotInstallerSteps is refactored to lead with - run: or - uses:), the condition is silently NOT injected — the installer runs unconditionally and the feature silently regresses to the old behaviour with no error, log warning, or CI signal.

💡 Suggested fix

Add an explicit warning log when a step is skipped so regressions surface in CI output:

if !strings.HasPrefix(step[0], "      - name:") {
    // gateStepsOnCopilotCached needs updating if GenerateCopilotInstallerSteps
    // ever changes its step shape.
    copilotInstallLog.Printf("WARNING: skipping copilot-cached gate for step with unexpected shape: %q", step[0])
    out = append(out, step)
    continue
}

A dedicated unit test asserting the if: line appears at index 1 of the gated step would also catch this without relying solely on golden tests that can be silently updated.

Comment thread actions/setup/compat.json
{
"max-gh-aw": "*",
"min-agent": "1.0.50",
"max-agent": "1.0.54"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bundled fallback matrix becomes stale after the first release past 1.0.54: max-agent: "1.0.54" is currently the ceiling of what the bundled matrix covers. Once runner images bake in 1.0.55+, any Copilot workflow where the live fetch fails (network outage, corporate proxy, self-hosted runner) will get zero cache hits from the bundled matrix and fall back to npm install — silently defeating the purpose of having a bundled fallback.

💡 Suggested fix

Either:

  1. Set max-agent to a deliberately high ceiling (e.g. "2.0.0") to make the bundled row long-lived. Combined with max-gh-aw: "*", this means the bundled matrix is always a safe fallback regardless of which version is cached.
  2. Add a release automation step that bumps min-agent/max-agent in compat.json as part of the gh-aw release process, keeping it in sync with what runner images bake.

Option 1 is simpler and lower maintenance. The version ceiling in the live matrix (at gh-aw-actions) is the authoritative source for fine-grained compatibility; the bundled matrix is only a last-resort fallback.

}

// Try live matrix first, fall back to bundled. Either may be null.
let matrix = await fetchLiveMatrix();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Network fetch happens before checking if any cached Copilot binary exists: fetchLiveMatrix() is called unconditionally whenever INPUT_INSTALL_COPILOT=true, even on runners where $RUNNER_TOOL_CACHE/copilot-cli/ is empty or missing. On a cache miss, the workflow waits up to 5 seconds for the raw.githubusercontent.com fetch to time out before concluding there is nothing to resolve.

💡 Suggested fix

Check whether $RUNNER_TOOL_CACHE/copilot-cli contains any entries before incurring the network round-trip:

async function resolve() {
  // ...
  // Fast-path: if the copilot-cli tool cache directory doesn't exist or is
  // empty, skip the network fetch entirely.
  const copilotCacheDir = path.join(toolCacheRoot, "copilot-cli");
  let hasEntries = false;
  try {
    hasEntries = fs.readdirSync(copilotCacheDir).length > 0;
  } catch { /* ENOENT or permission error — no cache */ }

  if (!hasEntries) {
    log("no copilot-cli in tool cache; skipping live matrix fetch");
    writeOutput("copilot-cached", "false");
    writeOutput("copilot-path", "");
    return;
  }

  let matrix = await fetchLiveMatrix();
  // ...
}

This makes the common case (runner images without the cache pre-populated) nearly instantaneous.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /tdd and /grill-with-docs — requesting changes on two correctness risks.

📋 Key Themes & Findings

Blocking

  • v-prefix in parseSemver (comment 1): if runner-images name toolcache directories v1.0.52 rather than 1.0.52, every cache lookup silently misses. This needs confirmation against the actual runner-image toolcache naming before merge.

  • gateStepsOnCopilotCached has no Go unit tests (comment 2): the function uses fragile strings.HasPrefix on raw YAML strings. A shape change in GenerateCopilotInstallerSteps would silently drop the if: gate from all Copilot workflows.

Non-blocking (but worth fixing)

  • Bundled compat.json will go stale (comment 3): max-agent: "1.0.54" is already close to the current version (1.0.52) with no documented update process. Recommend widening the ceiling or automating the bump on release.

  • resolve() has no unit tests (comment 4): the 23 vitest cases exercise helpers thoroughly but leave the top-level orchestration unwired. A few vi.stubEnv-based tests would prevent silent regressions.

  • condition constant lacks trailing \n (comment 5): cosmetic inconsistency with every other YAML-line string in the compiler, but the golden tests confirm it works correctly today.

Positive Highlights

  • ✅ Zero-dependency resolver — native fetch + fs only, no npm install required to run the resolver itself
  • ✅ Excellent fail-safe design: every error path exits 0 and writes copilot-cached=false, preserving the existing installer as fallback
  • ✅ Golden diff confined to Copilot fixtures only — compiler change is clearly mechanical and scoped
  • ✅ JSDoc typedefs and inline comments throughout install_copilot_cli.cjs make the intent clear
  • findCachedCopilot tests cover the toolcache edge cases (no .complete marker, missing binary, non-semver dirs) comprehensively

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 3.9M

*/
function parseSemver(v) {
if (typeof v !== "string") return null;
const m = v.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] parseSemver rejects v-prefixed version strings (e.g. "v1.0.52"), but runner-image toolcache directories are commonly named with a v prefix. If that is the naming convention used by install-copilot-cli.sh in runner-images, every toolcache lookup will silently miss, returning copilot-cached=false and falling back to npm install — defeating the feature entirely.

💡 Suggested fix + test

Strip a leading v before matching:

function parseSemver(v) {
  if (typeof v !== "string") return null;
  const normalised = v.startsWith("v") ? v.slice(1) : v;
  const m = normalised.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
  if (!m) return null;
  return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] || ""];
}

Add a test case:

it("strips leading v-prefix", () => {
  expect(parseSemver("v1.2.3")).toEqual([1, 2, 3, ""]);
});

Before merging, verify the actual directory names that install-copilot-cli.sh in runner-images creates (1.0.52 vs v1.0.52). The existing test expect(parseSemver("v1.2.3")).toBeNull() should be updated or removed once the convention is confirmed.

out = append(out, step)
continue
}
if !strings.HasPrefix(step[0], " - name:") {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] gateStepsOnCopilotCached has zero Go unit tests. It performs a subtle string-manipulation on the raw YAML lines of a GitHubActionStep — if GenerateCopilotInstallerSteps ever changes its step format (e.g., adds a comment line before - name:), the gate is silently dropped and every Copilot workflow skips the if: condition, potentially breaking the fallback path.

💡 Suggested test
func TestGateStepsOnCopilotCached(t *testing.T) {
    input := []GitHubActionStep{
        {"      - name: Install GitHub Copilot CLI\n", "        run: echo hello\n"},
    }
    got := gateStepsOnCopilotCached(input)
    require.Len(t, got, 1)
    assert.Equal(t, "      - name: Install GitHub Copilot CLI\n", got[0][0])
    assert.Contains(t, got[0][1], "if: steps.setup.outputs.copilot-cached != 'true'")
    assert.Equal(t, "        run: echo hello\n", got[0][2])
}

func TestGateStepsOnCopilotCached_NonNameStep(t *testing.T) {
    // Steps without - name: opener must be returned unmodified
    input := []GitHubActionStep{
        {"      - uses: actions/checkout@abc\n"},
    }
    got := gateStepsOnCopilotCached(input)
    require.Len(t, got, 1)
    assert.Equal(t, input[0], got[0], "non-name step should be unmodified")
}

Comment thread actions/setup/compat.json
"max-gh-aw": "*",
"min-agent": "1.0.50",
"max-agent": "1.0.54"
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/grill-with-docs] The bundled fallback matrix has max-agent: "1.0.54" with the project currently at 1.0.52. There is no documented process for bumping this ceiling. Once the project ships 1.0.55+, any runner without network access (or where the live matrix fetch fails) will silently miss the cached 1.0.55 CLI and fall back to npm install — the opposite of the feature's goal.

💡 Suggestions
  1. Document the update process: add a note in the _comment field or a CONTRIBUTING.md entry explaining that max-agent must be bumped on each release.
  2. Wider ceiling: consider setting max-agent to a generous upper bound (e.g. "99.99.99") and rely solely on max-gh-aw for compatibility gating. The fallback matrix is already behind a live-matrix fetch, so a stale ceiling is the primary failure mode.
  3. Automate: add a release step that updates compat.json max-agent to the new version before tagging.

});
});

describe("findCachedCopilot", () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] The resolve() orchestration function is exported but has no unit tests. The 23 cases cover individual helpers (semver, matrix parsing, cache scanning) well, but the end-to-end wiring is invisible to tests. Key paths that could regress silently include: RUNNER_TOOL_CACHE unset, both live and bundled matrices failing to load (null returned from both), and the case where rows.length === 0.

💡 Suggested test skeleton
describe("resolve", () => {
  beforeEach(() => {
    vi.stubEnv("RUNNER_TOOL_CACHE", "");
    vi.stubEnv("GITHUB_OUTPUT", "");
    vi.stubEnv("GITHUB_PATH", "");
    vi.stubEnv("INPUT_GH_AW_VERSION", "");
  });
  afterEach(() => vi.unstubAllEnvs());

  it("writes copilot-cached=false when RUNNER_TOOL_CACHE is unset", async () => {
    await resolve(); // should not throw
    // assert no output written (mocked GITHUB_OUTPUT not set)
  });
});

Even a smoke test that calls resolve() without throwing would prevent regressions in the top-level wiring.

// Steps without a recognisable ` - name:` opener are returned unmodified;
// any future refactor of the installer step shape should re-verify this here.
func gateStepsOnCopilotCached(steps []GitHubActionStep) []GitHubActionStep {
const condition = " if: steps.setup.outputs.copilot-cached != 'true'"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/grill-with-docs] The condition constant is the only YAML line string in this file (and likely in the codebase) that lacks a trailing \n. Every other YAML line emitted by the compiler ends with \n (e.g. " INPUT_INSTALL_COPILOT: 'true'\n"). The golden tests verify the output is correct today, but this inconsistency could confuse a future author who copies the pattern and inadvertently introduces a missing-newline bug when the surrounding step format changes.

Consider adding the trailing \n for consistency:

const condition = "        if: steps.setup.outputs.copilot-cached != 'true'\n"

@github-actions
Copy link
Copy Markdown
Contributor

@copilot review all comments and address the remaining requested changes. The ADR link has been added to the PR body.

Generated by 👨‍🍳 PR Sous Chef · gpt54 8.6M ·

@github-actions
Copy link
Copy Markdown
Contributor

@copilot review all comments and address unresolved review feedback. Also summarize the remaining blockers and rerun checks on the current branch.

Generated by 👨‍🍳 PR Sous Chef · gpt54 16M ·

@github-actions
Copy link
Copy Markdown
Contributor

@copilot refresh the branch and rerun checks, then summarize any remaining blockers.

Generated by 👨‍🍳 PR Sous Chef · gpt54 16.4M ·

Comment thread .github/workflows/ace-editor.lock.yml Outdated
GH_AW_INFO_AWF_VERSION: "v0.25.56"
GH_AW_INFO_ENGINE_ID: "copilot"
INPUT_INSTALL_COPILOT: 'true'
INPUT_GH_AW_VERSION: "947e72fa-dirty"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot only inject this field in release mode

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Addressed in 606f46e. INPUT_INSTALL_COPILOT / INPUT_GH_AW_VERSION are now emitted only in release/action mode (with install opt-in), and removed from dev-mode generated outputs (including ace-editor.lock.yml).

Copilot AI and others added 2 commits May 29, 2026 14:35
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title feat(setup): add toolcache resolver for Copilot CLI behind feature flag feat(setup): add Copilot CLI toolcache resolver with scoped release-mode opt-in May 29, 2026
Copilot AI requested a review from pelikhan May 29, 2026 14:54
@github-actions github-actions Bot mentioned this pull request May 29, 2026
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.

4 participants