Repository navigation
272 lines (242 loc) · 12.3 KB
/
Copy pathci.yml
File metadata and controls
272 lines (242 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
name: CI
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
jobs:
pr-title:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
pull-requests: read
steps:
- name: Validate PR title
uses: amannn/action-semantic-pull-request@v6.1.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
requireScope: false
types: |
feat
fix
docs
refactor
chore
test
ci
build
perf
revert
test:
runs-on: ubuntu-latest
# A wedged bun --parallel worker (oven-sh/bun#34069) can sit with no output
# forever; green runs take about 4 minutes and a timeout-stack run with the
# serial re-run about 15. Fail well before GitHub's 6-hour default so the
# babysit loop can see it and rerun.
timeout-minutes: 30
env:
# Single source of truth for the pinned `claude plugin validate` version:
# both the install and its cache key read this, so a bump cannot bump one
# and leave the other serving a stale cached binary.
CLAUDE_CODE_VERSION: 2.1.220
steps:
- uses: actions/checkout@v6
# Full history so release:validate can read the base-branch (origin/main)
# release-please manifest when checking release-as pins for staleness.
with:
fetch-depth: 0
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Validate release metadata
run: bun run release:validate
# Canonical schema check: runs `claude plugin validate --strict` against
# the marketplace catalog and the plugin manifest. The CLI must be
# installed via npm, not bun: `bun x` cannot run this package (bun blocks
# its required postinstall and cannot resolve its `claude` bin name), and
# `bun run` rewrites `npx` in package.json scripts to the broken `bun x`,
# so the script invokes a `claude` already on PATH instead.
#
# The validator version is pinned deliberately for reproducible CI: a
# floating @latest would track new upstream validation rules
# automatically but could break CI without any repo change. Bump the
# pin intentionally to adopt new `claude plugin validate` rules.
#
# plugin:validate must hit .claude-plugin/plugin.json — `validate .` only
# checks the marketplace. Root CLAUDE.md is a symlink to AGENTS.md so
# --strict stays green; see AGENTS.md "CI and Quality Gates".
#
# The install is cached because the npm package is a 165KB wrapper whose
# postinstall pulls a platform binary (`@anthropic-ai/claude-code-linux-x64`,
# ~275MB unpacked). Registry throughput for that payload is the whole cost
# of this step — validation itself is under a second. Observed range on
# ubuntu-latest: 3-10s typical, 5m worst case. Caching buys predictability,
# not average speed: it caps the tail rather than beating the fast path.
# Keying on the pinned version means a hit on every run between bumps.
- name: Cache claude-code validator
id: cache-claude-code
uses: actions/cache@v6
with:
path: ~/.npm-global
key: claude-code-${{ runner.os }}-${{ env.CLAUDE_CODE_VERSION }}
- name: Install claude-code validator
if: steps.cache-claude-code.outputs.cache-hit != 'true'
run: npm install -g --prefix ~/.npm-global @anthropic-ai/claude-code@${{ env.CLAUDE_CODE_VERSION }}
- name: Validate plugin schema (claude plugin validate)
run: |
export PATH="$HOME/.npm-global/bin:$PATH"
claude --version
bun run plugin:validate
# Runs the package `test` script so the flags CI uses and the flags a
# contributor gets from `bun run test` cannot drift apart.
- name: Run tests
run: bun run test
# Focused Windows gate for the skill scripts that reach for POSIX process and
# file primitives: peer-job-runner (#1243) and ce-babysit-pr's pr-snapshot
# (#1280). Not a full-suite matrix — POSIX lifecycle tests (fork/killpg/ps)
# stay on ubuntu-latest. This job runs the cross-platform fixtures under real
# win32, which is the only way to execute the msvcrt/ctypes branches at all:
# they are imported solely under sys.platform == "win32", so no amount of
# patching reaches them from Linux or Mac.
windows-native:
runs-on: windows-latest
# Green runs finish in about 90 seconds. The Windows smoke step has hung
# (2026-09-14, PR #1711 run 34909130752: 25+ minutes stuck, unresponsive to
# cancel and force-cancel; #1794). Fail well before GitHub's 6-hour default
# so the babysit loop can see it and rerun. The smoke step's own 6-minute
# deadline must fire before this cap kills the job and loses the log, so the
# cap leaves room for the setup steps ahead of it as well.
timeout-minutes: 15
steps:
# Keep LF in the working tree so newline-anchored parity regexes match.
# Default Windows runners often have core.autocrlf=true (see
# docs/solutions/developer-experience/windows-crlf-checkout-breaks-newline-anchored-tests.md).
- name: Disable autocrlf for LF checkout
run: git config --global core.autocrlf false
- uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
with:
# Match the verified Windows smoke environment for #1243.
python-version: "3.11"
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Peer-job-runner unit fixture
# Use `python`, never `python3` — on Windows the latter is often the
# Microsoft Store stub (see docs/solutions/conventions/resolve-python-interpreter-not-python3.md).
run: python tests/fixtures/peer-job-runner-unit.py
- name: Peer-job-runner Windows smoke
# Hosted windows-latest Python intermittently fails `import ctypes`
# mid-suite (DLL init on _ctypes) after earlier tests already passed.
# Product code cannot recover. Retries are signature-gated: only
# `DLL load failed while importing _ctypes` is retried; any other
# nonzero exit fails this step immediately. A retried attempt's log is
# suppressed so a green step never shows failure dumps; the full log
# prints on success and on the terminal failure.
#
# A 6-minute step deadline, under the job cap, makes a hang end the step
# with its log, Python stacks, and leftover processes instead of a killed
# job with no log (#1794). Output goes to files, not a pipe, so a
# leftover process holding an inherited handle cannot stall this shell.
shell: pwsh
run: |
$max = 3
$stepStart = Get-Date
$deadline = $stepStart.AddMinutes(6)
function Stop-StepProcesses {
# Descendants of this shell, plus orphans whose parent already exited
# (detached supervisors) running an executable the smoke launches; a
# start-time match alone also catches services.
$smokeExes = 'python.exe', 'bash.exe', 'sh.exe', 'env.exe'
$all = @(Get-CimInstance Win32_Process)
$alive = @{}
foreach ($proc in $all) { $alive[$proc.ProcessId] = $true }
$recent = @($all | Where-Object { $_.CreationDate -ge $stepStart -and $_.ProcessId -ne $PID })
$ours = @{}
do {
$added = 0
foreach ($proc in $recent) {
$parent = $proc.ParentProcessId
if (-not $ours[$proc.ProcessId] -and ($parent -eq $PID -or $ours[$parent] -or (-not $alive[$parent] -and $smokeExes -contains $proc.Name))) {
$ours[$proc.ProcessId] = $true
$added++
}
}
} while ($added -gt 0)
$procs = @($recent | Where-Object { $ours[$_.ProcessId] })
if ($procs.Count -eq 0) { Write-Host "No processes started by this step are still running."; return }
Write-Host "Processes started by this step that are still running:"
$procs | Format-Table ProcessId, ParentProcessId, CreationDate, Name, CommandLine -AutoSize -Wrap | Out-String -Width 400 | Write-Host
$procs | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
}
for ($i = 1; $i -le $max; $i++) {
$out = Join-Path $env:TEMP "peer-job-runner-windows-smoke-$i.out.log"
$err = Join-Path $env:TEMP "peer-job-runner-windows-smoke-$i.err.log"
$remaining = [int][math]::Floor(($deadline - (Get-Date)).TotalSeconds)
$env:PEER_SMOKE_DEADLINE_SECS = [math]::Max($remaining - 30, 10)
$p = Start-Process python -ArgumentList 'tests/fixtures/peer-job-runner-windows-smoke.py' `
-NoNewWindow -PassThru -RedirectStandardOutput $out -RedirectStandardError $err
# Start-Process leaves ExitCode empty unless the handle is opened while the process runs.
$null = $p.Handle
$finished = $p.WaitForExit([math]::Max($remaining, 1) * 1000)
$text = (@($err, $out) | ForEach-Object { Get-Content -Raw -ErrorAction SilentlyContinue $_ }) -join "`n"
if (-not $finished) {
if ($text) { Write-Host $text }
Write-Host "::error::Windows smoke attempt $i was still running at the step's 6-minute deadline (#1794)"
Stop-StepProcesses
exit 1
}
$code = $p.ExitCode
if (($code -eq 0) -or ($text -notmatch 'DLL load failed while importing _ctypes') -or ($i -eq $max)) {
if ($text) { Write-Host $text }
if ($code -ne 0) { Stop-StepProcesses }
exit $code
}
Write-Host "Windows smoke hit known _ctypes flake (attempt $i/$max, exit $code); output suppressed, retrying"
Start-Sleep -Seconds 15
}
- name: Peer-job-runner parity
run: bun test tests/peer-job-runner-parity.test.ts
- name: pr-snapshot platform primitives
run: python tests/fixtures/pr-snapshot-platform.py
# Runs the scratch-root preamble exactly as the skills ship it, under Git Bash. The
# POSIX suite cannot cover this: `install -d -m 700` works there and fails only here
# (#1285), so a shape assertion stayed green while every skill's scratch setup aborted
# on Windows. Shell out via bun so the same file also runs in the ubuntu suite.
- name: Scratch-root preamble executes under Git Bash
run: bun test tests/scratch-root-preamble-executes.test.ts
# The cross-model workers' skip-evidence helper once stalled for minutes on macOS
# /bin/bash 3.2 (#1785). Ubuntu's bash 5 cannot reproduce that, so this job runs the
# guard under the real 3.2 and fails if the image stops shipping it.
macos-bash32:
runs-on: macos-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
# Call /bin/bash explicitly: the runner's PATH bash is Homebrew bash 5.
- name: Require bash 3.x at /bin/bash
run: |
/bin/bash -c 'echo "/bin/bash is $BASH_VERSION"; case "$BASH_VERSION" in 3.*) ;; *) echo "::error::/bin/bash is not bash 3.x, so this job cannot observe the bash 3.2 regression"; exit 1 ;; esac'
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install
# bun exits 0 when the named test is skipped, so the summary must show it ran.
# An explicit `shell: bash` adds pipefail, so bun's exit status survives `| tee`.
- name: Failure-evidence guard under bash 3.2
shell: bash
run: |
log="$RUNNER_TEMP/guard.log"
bun test tests/skills/cross-model-peer-budget.test.ts -t "cross-model failure evidence" 2>&1 | tee "$log"
grep -Eq '^ *[1-9][0-9]* pass$' "$log" || { echo "::error::the failure-evidence guard did not run"; exit 1; }
if grep -Eq '^ *[1-9][0-9]* (skip|todo)$' "$log"; then echo "::error::the failure-evidence guard was skipped"; exit 1; fi