Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Pin Copilot CLI version across all CI paths via pom.xml
Previously, three different code paths determined the Copilot CLI version
inconsistently:

- Path A (build-test.yml) installed the CLI from the cloned reference impl
  pinned by .lastmerge — reproducible.
- Path B (run-smoke-test.yml, update-copilot-dependency.yml via the
  setup-copilot action) ran `npm install -g @github/copilot` with no
  version pin — floated to whatever was latest on npm.
- Path C (local dev) fell through to whatever `copilot` was on PATH
  (often whatever VS Code Insiders had installed).

When the CLI protocol vocabulary changed between 1.0.35 and 1.0.39, this
inconsistency made it hard to land reference-impl merges that required a
specific CLI version.

This change makes pom.xml the single source of truth for the
@github/copilot version that all paths must use, while keeping .lastmerge
as the source of truth for the reference implementation commit.

Changes:

- pom.xml: replace the PRIMER_TO_REPLACE placeholder in the property
  <readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-weekly-reference-impl-sync>
  with the current value (^1.0.36-0) extracted from the cloned
  reference impl's nodejs/package.json. Add a doc-comment explaining
  that the property is auto-managed and is the canonical CLI version
  for all CI paths.

- .github/scripts/reference-impl-sync/sync-cli-version-from-reference-impl.sh
  (new): reads dependencies."@github/copilot" from the reference impl's
  nodejs/package.json (via node) and rewrites the pom.xml property in
  place. Locates the repo root by walking up from the script's own
  location until it finds a pom.xml, so it is resilient to relocation
  and works from any CWD. Uses portable sed for the rewrite.

- .github/scripts/reference-impl-sync/merge-reference-impl-finish.sh:
  call the new sync script right after writing .lastmerge, and stage
  pom.xml alongside .lastmerge in the same commit. .lastmerge and the
  CLI version property now always move together.

- .github/actions/setup-copilot/action.yml: read the pinned version from
  pom.xml using sed and run `npm install -g "@github/copilot@VERSION"`
  instead of installing the latest. Fail fast if the property is unset
  or still the primer. Expose the resolved version as an action output.

- CONTRIBUTING.md: document the local-dev workflow that mirrors what
  build-test.yml does in CI. The cloned reference impl contains two
  separate package.json files that both pin @github/copilot:

    * target/copilot-sdk/nodejs/package.json (^1.0.36-0) — the SDK-test
      pin; this is the CLI the tests must run against.
    * target/copilot-sdk/test/harness/package.json (^1.0.32) — the
      replay-harness pin; incidental, NOT the CLI under test.

  `mvn generate-test-resources` runs `npm install` only in the harness
  subtree, so a generic `find target -path '*/@github/copilot/index.js'`
  picks the wrong (older) copy. The instructions now:
    1. Run `npm ci` in target/copilot-sdk/nodejs/ explicitly.
    2. Scope the COPILOT_CLI_PATH lookup to '*/nodejs/node_modules/...'
       so the harness copy can never be selected, even if both are
       present. Both POSIX (find) and PowerShell (Get-ChildItem) forms
       are tightened.

- .gitignore: minor housekeeping.

Verified mvn clean package -Pskip-test-harness -DskipTests succeeds with
the new POM, the sync script correctly rewrites the property when invoked
from any CWD, and the tightened CONTRIBUTING.md sequence resolves
COPILOT_CLI_PATH to the 1.0.36-0 CLI under target/copilot-sdk/nodejs.
  • Loading branch information
edburns committed Apr 29, 2026
commit 477398ee61e47bf47d2b1e5f01959abba8951695
25 changes: 23 additions & 2 deletions .github/actions/setup-copilot/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,36 @@ outputs:
cli-path:
description: "Path to the Copilot CLI executable"
value: ${{ steps.cli-path.outputs.path }}
cli-version:
description: "Pinned @github/copilot version installed (read from pom.xml)"
value: ${{ steps.cli-version.outputs.version }}
runs:
using: "composite"
steps:
- uses: actions/setup-node@v6
Comment thread
edburns marked this conversation as resolved.
Outdated
with:
node-version: 22
- name: Install Copilot CLI
run: npm install -g @github/copilot
- name: Read pinned @github/copilot version from pom.xml
id: cli-version
shell: bash
# The version is the SINGLE SOURCE OF TRUTH for the Copilot CLI version
# used across all CI paths. It is kept in sync with the reference
# implementation pinned in .lastmerge by
# .github/scripts/reference-impl-sync/sync-cli-version-from-reference-impl.sh.
run: |
PROP="readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-weekly-reference-impl-sync"
VERSION=$(sed -n "s|.*<${PROP}>\(.*\)</${PROP}>.*|\1|p" pom.xml | head -n 1 | tr -d '[:space:]')
if [[ -z "$VERSION" || "$VERSION" == "PRIMER_TO_REPLACE" ]]; then
echo "::error::Could not read pinned @github/copilot version from pom.xml property <${PROP}>" >&2
exit 1
fi
echo "Pinned @github/copilot version: $VERSION"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Install Copilot CLI (pinned to pom.xml version)
shell: bash
env:
CLI_VERSION: ${{ steps.cli-version.outputs.version }}
run: npm install -g "@github/copilot@${CLI_VERSION}"
Comment thread
edburns marked this conversation as resolved.
- name: Set CLI path
id: cli-path
run: echo "path=$(which copilot)" >> $GITHUB_OUTPUT
Expand Down
14 changes: 11 additions & 3 deletions .github/scripts/reference-impl-sync/merge-reference-impl-finish.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
# Finalises a reference implementation merge:
# 1. Runs format + test + build (via format-and-test.sh)
# 2. Updates .lastmerge to reference implementation HEAD
# 3. Commits the .lastmerge update
# 4. Pushes the branch to origin
# 3. Syncs the @github/copilot version property in pom.xml from the
# cloned reference implementation's nodejs/package.json
# 4. Commits the .lastmerge + pom.xml updates
# 5. Pushes the branch to origin
#
# Usage: ./.github/scripts/reference-impl-sync/merge-reference-impl-finish.sh
# ./.github/scripts/reference-impl-sync/merge-reference-impl-finish.sh --skip-tests
Expand Down Expand Up @@ -48,7 +50,13 @@ echo "▸ Updating .lastmerge…"
NEW_COMMIT=$(cd "$REFERENCE_IMPL_DIR" && git rev-parse origin/main)
echo "$NEW_COMMIT" > "$ROOT_DIR/.lastmerge"

git add .lastmerge
# ── 2b. Sync pom.xml @github/copilot version ─────────────────
# Keeps the canonical CLI version in pom.xml aligned with what the
# reference implementation pinned in .lastmerge depends on.
echo "▸ Syncing @github/copilot version in pom.xml from reference implementation…"
"$ROOT_DIR/.github/scripts/reference-impl-sync/sync-cli-version-from-reference-impl.sh" "$REFERENCE_IMPL_DIR"

git add .lastmerge pom.xml
git commit -m "Update .lastmerge to $NEW_COMMIT"
Comment thread
edburns marked this conversation as resolved.
Outdated

# ── 3. Push branch ───────────────────────────────────────────
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# ──────────────────────────────────────────────────────────────
# sync-cli-version-from-reference-impl.sh
#
# Reads the @github/copilot version specifier from the cloned
# reference implementation's nodejs/package.json, and updates the
# corresponding property in pom.xml:
#
# <readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-weekly-reference-impl-sync>
#
# This keeps the canonical Copilot CLI version (declared in pom.xml)
# in sync with whatever the reference implementation pinned in
# .lastmerge depends on. All workflows that install the Copilot CLI
# (build-test.yml — implicitly via cloned SDK, run-smoke-test.yml and
# update-copilot-dependency.yml — via the setup-copilot action) read
# this single property so every CI path uses the same CLI version.
#
# Usage:
# ./sync-cli-version-from-reference-impl.sh <reference-impl-dir>
#
# Or, when invoked from merge-reference-impl-finish.sh, sources
# REFERENCE_IMPL_DIR from the .merge-env file.
# ──────────────────────────────────────────────────────────────
set -euo pipefail

# Locate the repo root by walking up from this script until we find a pom.xml.
# This is resilient to the script being moved to a different depth under
# .github/scripts/ in the future.
find_repo_root() {
local dir
dir="$(cd "$(dirname "$0")" && pwd)"
while [[ "$dir" != "/" ]]; do
if [[ -f "$dir/pom.xml" ]]; then
echo "$dir"
return 0
fi
dir="$(dirname "$dir")"
done
echo "❌ Could not locate repo root (no pom.xml found above $(dirname "$0"))" >&2
return 1
}
ROOT_DIR="$(find_repo_root)"

REFERENCE_IMPL_DIR="${1:-${REFERENCE_IMPL_DIR:-}}"
if [[ -z "$REFERENCE_IMPL_DIR" ]]; then
echo "❌ Usage: $0 <reference-impl-dir>" >&2
echo " or set REFERENCE_IMPL_DIR in the environment." >&2
exit 1
fi

PKG_JSON="$REFERENCE_IMPL_DIR/nodejs/package.json"
if [[ ! -f "$PKG_JSON" ]]; then
echo "❌ Cannot find $PKG_JSON" >&2
exit 1
fi

# node is always available since the reference implementation uses npm.
CLI_VERSION=$(node -e \
"const fs=require('fs');const p=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));const v=(p.dependencies&&p.dependencies['@github/copilot'])||(p.devDependencies&&p.devDependencies['@github/copilot']);if(!v){process.exit(2);}process.stdout.write(v);" \
Comment thread
edburns marked this conversation as resolved.
Outdated
"$PKG_JSON")
Comment thread
edburns marked this conversation as resolved.

if [[ -z "$CLI_VERSION" ]]; then
echo "❌ Could not extract @github/copilot version from $PKG_JSON" >&2
exit 1
fi

POM="$ROOT_DIR/pom.xml"
PROP="readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-weekly-reference-impl-sync"

if ! grep -q "<${PROP}>" "$POM"; then
echo "❌ Property <${PROP}> not found in $POM" >&2
exit 1
fi

# Use a portable sed invocation (works on both BSD/macOS and GNU/Linux).
TMP="$(mktemp)"
sed -E "s|<${PROP}>[^<]*</${PROP}>|<${PROP}>${CLI_VERSION}</${PROP}>|" "$POM" > "$TMP"
mv "$TMP" "$POM"

echo "▸ Updated pom.xml: <${PROP}> = ${CLI_VERSION}"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ changebundle.txt*
.settings
scripts/codegen/node_modules/
*~
*.sln
48 changes: 47 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,41 @@ If you have ideas for entirely new features, please post an issue or start a dis
1. Push to your fork and [submit a pull request][pr]
1. Pat yourself on the back and wait for your pull request to be reviewed and merged.

### Running tests and linters
### Running locally, including tests and linters

```bash
# Obtain the pinned version of Copilot CLI to the local workarea.
# This clones the reference implementation at the commit pinned in
# .lastmerge, but does NOT run `npm ci` inside its nodejs/ subdir.
mvn generate-test-resources

# Install the pinned Copilot CLI into target/copilot-sdk/nodejs/node_modules
# so the SDK tests use the version declared in
# target/copilot-sdk/nodejs/package.json (the SDK-test pin), NOT the version
# in target/copilot-sdk/test/harness/package.json (the replay-harness pin,
# which is incidental and may be older).

## POSIX

(cd target/copilot-sdk/nodejs && npm ci --ignore-scripts)

## PowerShell

Push-Location target\copilot-sdk\nodejs; if ($?) { npm ci --ignore-scripts }; Pop-Location

# Make it so the pinned Copilot CLI is used for the tests. The patterns
# below are scoped to the `nodejs/node_modules/` subtree so they cannot
# accidentally pick up the older harness copy under
# target/copilot-sdk/test/harness/node_modules/.

## POSIX

export COPILOT_CLI_PATH="$(find "$PWD/target" -type f -path '*/nodejs/node_modules/@github/copilot/index.js' | head -n 1)"

## PowerShell

$env:COPILOT_CLI_PATH = (Get-ChildItem -Path "$PWD\target" -Recurse -Filter 'index.js' -File | Where-Object { $_.FullName -match '[\\/]nodejs[\\/]node_modules[\\/]@github[\\/]copilot[\\/]index\.js$' } | Select-Object -First 1 -ExpandProperty FullName)

# Build and run all tests
mvn clean verify

Expand All @@ -54,6 +86,20 @@ mvn spotless:apply
mvn spotless:check
```

Assuming you are in the same shell you used to run the above commands, to run this exact Copilot CLI locally you can do the following.

### POSIX

```bash
node ${COPILOT_CLI_PATH}
```
Comment thread
edburns marked this conversation as resolved.

### PowerShell

```PowerShell
node $env:COPILOT_CLI_PATH
```

Here are a few things you can do that will increase the likelihood of your pull request being accepted:

- Write tests.
Expand Down
21 changes: 21 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,27 @@
<skip.test.harness>false</skip.test.harness>
<!-- Extra JVM args for Surefire; overridden by the jdk21+ profile -->
<surefire.jvm.args />
<!--
The version of the @github/copilot npm package that the reference implementation
commit pinned in .lastmerge depends on. Mirrors the value of dependencies."@github/copilot"
in target/copilot-sdk/nodejs/package.json after the reference impl is cloned/reset to the
commit in .lastmerge.

The previously mentioned package.json contains the SINGLE
SOURCE OF TRUTH for the Copilot CLI version that all paths
(build-test.yml, run-smoke-test.yml,
update-copilot-dependency.yml, setup-copilot action) must
pin to. It is updated automatically by
.github/scripts/reference-impl-sync/sync-cli-version-from-reference-impl.sh,
which is called from merge-reference-impl-finish.sh
whenever .lastmerge is updated.

DO NOT EDIT MANUALLY. To update, run the
reference-impl-sync workflow and deal with the subsequent
PR.
-->
<readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-weekly-reference-impl-sync>^1.0.36-0</readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-weekly-reference-impl-sync>
Comment thread
edburns marked this conversation as resolved.

</properties>

<dependencies>
Expand Down
Loading