Skip to content

Commit 1fadec0

Browse files
TheLarkInnCopilot
andcommitted
ci(gh-aw): add CI guard for lock-file drift and compiler-version skew
Add GH_AW_VERSION env var to ci.yml as the single source of truth for the pinned gh-aw compiler version, and an aw-lock-drift job that installs the pinned extension, recompiles all agentic workflows, and fails if: - any .lock.yml or .github/aw file drifts from its .md source - gh aw compile produces untracked files - lock files disagree on compiler_version or differ from the pin Update CLAUDE.md to document the pin and the drift job, with guidance to install the pinned version instead of upgrading to latest. Closes #1390 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 6d3368d commit 1fadec0

2 files changed

Lines changed: 74 additions & 3 deletions

File tree

.github/workflows/ci.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ env:
1313
CARGO_NET_RETRY: 10
1414
RUST_BACKTRACE: short
1515
LIBGIT2_SYS_USE_PKG_CONFIG: "1"
16+
# Single source of truth for the pinned gh-aw compiler version. Every
17+
# .github/workflows/*.lock.yml must be compiled with this exact version —
18+
# the aw-lock-drift job enforces it. To bump: change this value, install
19+
# with `gh extension install github/gh-aw --pin <version>` (NOT
20+
# `gh extension upgrade gh-aw`, which floats to latest), run a bare
21+
# `gh aw compile`, and commit every regenerated lock file together.
22+
GH_AW_VERSION: "v0.86.2"
1623

1724
jobs:
1825
ci:
@@ -48,6 +55,52 @@ jobs:
4855
- name: Format
4956
run: cargo fmt --check
5057

58+
aw-lock-drift:
59+
name: Agentic workflow lock drift
60+
runs-on: ubuntu-latest
61+
steps:
62+
- uses: actions/checkout@v4
63+
64+
- name: Install pinned gh-aw extension
65+
run: gh extension install github/gh-aw --pin "$GH_AW_VERSION"
66+
env:
67+
GH_TOKEN: ${{ github.token }}
68+
69+
- name: Recompile agentic workflows
70+
run: gh aw compile
71+
env:
72+
GH_TOKEN: ${{ github.token }}
73+
74+
- name: Fail on lock-file drift or untracked compiler output
75+
run: |
76+
if ! git diff --exit-code -- .github/workflows .github/aw; then
77+
echo "::error::Lock files are out of date. Install the pinned gh-aw ('gh extension install github/gh-aw --pin $GH_AW_VERSION'), run 'gh aw compile', and commit the result."
78+
exit 1
79+
fi
80+
UNTRACKED=$(git status --porcelain --untracked-files=all -- .github/workflows)
81+
if [ -n "$UNTRACKED" ]; then
82+
echo "::error::gh aw compile produced untracked files. Commit them, or delete the orphaned .md source that generated them:"
83+
echo "$UNTRACKED"
84+
exit 1
85+
fi
86+
87+
- name: Assert every lock reports the pinned compiler version
88+
run: |
89+
VERSIONS=$(grep -ho '"compiler_version":"[^"]*"' .github/workflows/*.lock.yml | sort -u || true)
90+
if [ -z "$VERSIONS" ]; then
91+
echo "::error::No compiler_version found in any .github/workflows/*.lock.yml header."
92+
exit 1
93+
fi
94+
if [ "$(printf '%s\n' "$VERSIONS" | wc -l)" -ne 1 ]; then
95+
echo "::error::Lock files report multiple gh-aw compiler versions — recompile ALL workflows with the pinned version ($GH_AW_VERSION):"
96+
printf '%s\n' "$VERSIONS"
97+
exit 1
98+
fi
99+
if [ "$VERSIONS" != "\"compiler_version\":\"$GH_AW_VERSION\"" ]; then
100+
echo "::error::Locks were compiled with $VERSIONS but GH_AW_VERSION=$GH_AW_VERSION is pinned. Install it ('gh extension install github/gh-aw --pin $GH_AW_VERSION'), run 'gh aw compile', and commit."
101+
exit 1
102+
fi
103+
51104
coverage:
52105
name: Coverage (89% branch gate)
53106
runs-on: ubuntu-latest

CLAUDE.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,19 @@ Never hand-edit a `.lock.yml`: commit both the `.md` source and the regenerated
119119

120120
#### Keep every lock file on the same compiler version
121121

122-
**All lock files must be compiled with the same `gh aw` version.** The repository is currently on **`v0.86.2`**. The pin is recorded in every lock file's `# gh-aw-metadata:` header as `compiler_version` — there is no separate pin file; the locks themselves are the record. Check yours with `gh aw version` and upgrade with `gh extension upgrade gh-aw` before recompiling.
122+
**All lock files must be compiled with the same `gh aw` version.** The repository is currently on **`v0.86.2`**. The pin lives in the `GH_AW_VERSION` env var at the top of `.github/workflows/ci.yml` — that is the single source of truth, and every lock file's `# gh-aw-metadata:` header must report the same `compiler_version`.
123+
124+
Install the **pinned** version before recompiling:
125+
126+
```bash
127+
gh extension install github/gh-aw --pin v0.86.2 # use the GH_AW_VERSION value from ci.yml
128+
```
129+
130+
Do **NOT** run `gh extension upgrade gh-aw` — upgrading floats to the latest release, and if latest differs from the pin your recompiled locks will fail the `aw-lock-drift` CI job spuriously. Check what you have with `gh aw version`.
123131

124132
Recompiling a single workflow with a newer extension than the others introduces *compiler version skew*. Each lock file embeds a pinned [`gh-aw-firewall`](https://github.com/githubnext/gh-aw-firewall) (AWF) release, so a skewed lock ends up on a different AWF pin than its siblings. Upstream deletes old AWF releases, and when that happens the `Install AWF binary` step dies with `curl: (22) ... 404` and the workflow fails 100% of the time — which is exactly how `reverse-binary-analysis` (pinned to the deleted `v0.25.28`) silently failed every week for over two months ([#1388](https://github.com/TheLarkInn/aipm/issues/1388)).
125133

126-
When upgrading the extension, run a bare `gh aw compile` to recompile **all** workflows at once, and confirm the pins agree before committing:
134+
When bumping the pin, update `GH_AW_VERSION` in `ci.yml`, install that version, run a bare `gh aw compile` to recompile **all** workflows at once, and confirm the AWF pins agree before committing:
127135

128136
```bash
129137
grep -ho 'install_awf_binary\.sh" v[0-9.]*' .github/workflows/*.lock.yml | awk '{print $2}' | sort -u
@@ -135,6 +143,16 @@ That must print exactly one version, and it must resolve upstream:
135143
gh api repos/githubnext/gh-aw-firewall/releases/tags/<version> --jq .tag_name
136144
```
137145

146+
#### The `aw-lock-drift` CI job enforces all of this
147+
148+
The `aw-lock-drift` job in `ci.yml` fails the build when the committed locks drift from the `.md` sources or the pin. It installs gh-aw at exactly `GH_AW_VERSION` (`gh extension install github/gh-aw --pin`), runs `gh aw compile`, and fails if:
149+
150+
- `git diff --exit-code -- .github/workflows .github/aw` shows any change — an `.md` was edited (or gh-aw bumped) without committing the regenerated `.lock.yml` ([#1390](https://github.com/TheLarkInn/aipm/issues/1390))
151+
- `git status --porcelain --untracked-files=all -- .github/workflows` is non-empty — compile emitted an untracked file, the orphaned-source class of bug
152+
- the `# gh-aw-metadata:` headers disagree on `compiler_version`, or report anything other than `GH_AW_VERSION` — compiler-version skew
153+
154+
If it fails on your PR, install the pinned version and recompile as shown above, then commit every regenerated file.
155+
138156
#### Lock files are generated — `.gitattributes` is deliberately minimal
139157

140158
`.gitattributes` carries exactly one rule for the compiled locks:
@@ -143,7 +161,7 @@ gh api repos/githubnext/gh-aw-firewall/releases/tags/<version> --jq .tag_name
143161
.github/workflows/*.lock.yml linguist-generated=true
144162
```
145163

146-
`linguist-generated=true` keeps the generated locks out of PR diffs and repo language stats. The rule **deliberately does not** set `merge=ours` ([#1392](https://github.com/TheLarkInn/aipm/issues/1392)): `merge=ours` only works when every contributor configures the `ours` merge driver locally (`git config merge.ours.driver true`), so it was inert for most clones, and when it *did* fire it silently resolved lock conflicts to the local side instead of regenerating from the `.md` source — precisely the drift this section exists to prevent. A conflict in a lock file must be resolved by recompiling (`gh aw compile`), never by keeping one side, so a loud conflict is the desired behaviour. The follow-up CI guard in [#1390](https://github.com/TheLarkInn/aipm/issues/1390) makes out-of-date locks fail the build outright.
164+
`linguist-generated=true` keeps the generated locks out of PR diffs and repo language stats. The rule **deliberately does not** set `merge=ours` ([#1392](https://github.com/TheLarkInn/aipm/issues/1392)): `merge=ours` only works when every contributor configures the `ours` merge driver locally (`git config merge.ours.driver true`), so it was inert for most clones, and when it *did* fire it silently resolved lock conflicts to the local side instead of regenerating from the `.md` source — precisely the drift this section exists to prevent. A conflict in a lock file must be resolved by recompiling (`gh aw compile`), never by keeping one side, so a loud conflict is the desired behaviour. The `aw-lock-drift` CI guard ([#1390](https://github.com/TheLarkInn/aipm/issues/1390)) makes out-of-date locks fail the build outright.
147165

148166
#### `agentics-maintenance.yml` is generated and adopted
149167

0 commit comments

Comments
 (0)