Skip to content

Commit ca1cc53

Browse files
authored
Pin reusable workflow helper revision (#1756)
1 parent 54c2f9a commit ca1cc53

3 files changed

Lines changed: 48 additions & 17 deletions

File tree

.github/workflows/run-agent-task.yml

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ name: Run Agent Task (reusable)
149149
jobs:
150150
run-agent-task:
151151
runs-on: ubuntu-latest
152+
env:
153+
# Advance this only in a subsequent workflow commit after helper changes land.
154+
WP_CODEBOX_HELPER_REVISION: 54c2f9a7bc3cd1fe20055d496c83efcfb99afb41
152155
permissions:
153156
contents: write
154157
pull-requests: write
@@ -161,29 +164,20 @@ jobs:
161164
credential_mode: ${{ steps.execute.outputs.credential_mode }}
162165
declared_artifacts_json: ${{ steps.execute.outputs.declared_artifacts_json }}
163166
steps:
164-
- name: Resolve WP Codebox workflow ref
165-
id: workflow-ref
166-
env:
167-
# `github.workflow_sha` is the commit SHA of THIS reusable workflow
168-
# file, so the checked-out helper scripts always match the version of
169-
# run-agent-task.yml that is executing. `github.workflow_ref` must not be
170-
# used here: inside a reusable workflow it resolves to the caller's
171-
# top-level workflow ref, so its branch name (e.g. `trunk` for a caller
172-
# whose default branch is not `main`) gets used to check out
173-
# Automattic/wp-codebox and fails when that ref does not exist here.
174-
JOB_WORKFLOW_SHA: ${{ github.workflow_sha }}
167+
- name: Validate pinned WP Codebox helper revision
175168
run: |
176-
ref="${JOB_WORKFLOW_SHA}"
177-
if [ -z "${ref}" ]; then
178-
ref="main"
169+
if ! [[ "${WP_CODEBOX_HELPER_REVISION}" =~ ^[0-9a-f]{40}$ ]]; then
170+
echo "WP_CODEBOX_HELPER_REVISION must be a full lowercase Git commit SHA" >&2
171+
exit 1
179172
fi
180-
printf 'ref=%s\n' "${ref}" >> "$GITHUB_OUTPUT"
181173
182174
- name: Checkout WP Codebox workflow helpers
183175
uses: actions/checkout@v4
184176
with:
185177
repository: Automattic/wp-codebox
186-
ref: ${{ steps.workflow-ref.outputs.ref }}
178+
# This checkout fetches the exact immutable helper source and verifies
179+
# its reachability independently of the caller's workflow revision.
180+
ref: ${{ env.WP_CODEBOX_HELPER_REVISION }}
187181
path: .wp-codebox-workflow
188182

189183
- name: Set up Node.js

docs/agent-task-reusable-workflow.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ public native package, invokes the package-declared agent through the native cha
3030
in a credential-free verification environment, and returns actual runtime and
3131
publication data.
3232
33+
## Helper Source Revision
34+
35+
The workflow checks out its helper scripts from one immutable WP Codebox commit
36+
configured as `WP_CODEBOX_HELPER_REVISION`. This source revision is independent
37+
of the caller repository, caller commit, and future revisions of
38+
`run-agent-task.yml`; no GitHub workflow SHA context selects helper source.
39+
40+
Helper changes land first. A subsequent commit that updates this reusable
41+
workflow advances the helper revision to the full SHA containing those merged
42+
files. The workflow validates the full-SHA shape and `actions/checkout` fetches
43+
that exact source before running helpers. The regression test reads the required
44+
helper files at the pinned revision and checks their SHA-256 digests.
45+
46+
This pin fixes [#1755](https://github.com/Automattic/wp-codebox/issues/1755) and the failed Build callers [29281470179](https://github.com/Automattic/build-with-wordpress/actions/runs/29281470179)
47+
and [29281470159](https://github.com/Automattic/build-with-wordpress/actions/runs/29281470159), where a foreign caller SHA was incorrectly used as a WP Codebox checkout ref.
48+
3349
## Inputs
3450

3551
- `external_package_source`: immutable descriptor with `repository`, full commit `revision`, one package-relative `.agent.json` `path`, and `digest`. Packages are supported only from publicly accessible GitHub repositories, fetched from canonical `https://github.com/OWNER/REPOSITORY.git` without credentials. `digest` is exactly `sha256-bytes-v1:<lowercase-sha256>` over the raw file bytes; filenames and JSON content are UTF-8-safe and are not normalized before hashing.

tests/agent-task-reusable-workflow.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import assert from "node:assert/strict"
22
import { execFile } from "node:child_process"
3+
import { createHash } from "node:crypto"
34
import { mkdir, mkdtemp, readFile, readdir, symlink, writeFile } from "node:fs/promises"
45
import { tmpdir } from "node:os"
56
import { join } from "node:path"
@@ -38,6 +39,26 @@ assert.match(workflow, /agent-task-artifacts/)
3839
assert.match(workflow, /prepare-agent-task-upload\.mjs/)
3940
assert.match(workflow, /agent-task-upload/)
4041
assert.match(workflow, /if: always\(\)/)
42+
const helperRevision = workflow.match(/WP_CODEBOX_HELPER_REVISION:\s*([0-9a-f]{40})/)?.[1]
43+
assert.ok(helperRevision, "The workflow must declare one full immutable WP Codebox helper revision")
44+
assert.match(workflow, /ref: \$\{\{ env\.WP_CODEBOX_HELPER_REVISION \}\}/)
45+
assert.doesNotMatch(workflow, /github\.(?:workflow_sha|workflow_ref)/)
46+
assert.doesNotMatch(workflow, /steps\.[^.]+\.outputs\.ref/)
47+
const foreignCallerSha = "ba2df8c2215407b1a58edbff29e3ddcb5efa2249"
48+
assert.notEqual(helperRevision, foreignCallerSha, "A caller repository SHA must not select WP Codebox helpers")
49+
assert.doesNotMatch(workflow, new RegExp(foreignCallerSha))
50+
assert.match(workflow, /Validate pinned WP Codebox helper revision/)
51+
52+
const requiredHelperFiles = {
53+
".github/scripts/run-agent-task/build-codebox-task-request.mjs": "a0b9052bf63b0d5b096568c21a1e7333b7474b35834ffd1bc0d748fb0cfd0859",
54+
".github/scripts/run-agent-task/execute-native-agent-task.mjs": "b67910e538c6fb231c25dee43d0ff2e06e521e08108f8570e0389b022f9f8ccc",
55+
".github/scripts/run-agent-task/prepare-agent-task-upload.mjs": "93511e4174e705f55ff014ba68ac52fdaca771ce3f19f3921cceb661e69569da",
56+
}
57+
for (const [path, digest] of Object.entries(requiredHelperFiles)) {
58+
const { stdout } = await execFileAsync("git", ["show", `${helperRevision}:${path}`], { encoding: "buffer" })
59+
const actualDigest = createHash("sha256").update(stdout).digest("hex")
60+
assert.equal(actualDigest, digest, `Pinned helper source changed unexpectedly: ${path}`)
61+
}
4162
assert.doesNotMatch(publicWorkflowSurface, /step_budget:|tool_results_key:/)
4263
assert.doesNotMatch(workflow, /steps\.plan\.outputs/)
4364
assert.doesNotMatch(publicWorkflowSurface, /datamachine|data machine|data-machine|agents api/i)
@@ -57,7 +78,7 @@ assert.match(docs, /run-agent-task-reusable-workflow-interface\.v1\.json/)
5778
assert.match(docs, /WP_CODEBOX_DIR/)
5879
assert.match(docs, /Runtime Coverage/)
5980
assert.match(docs, /not a WordPress Playground end-to-end test/)
60-
assert.doesNotMatch(docs, /docs-agent|wp-codebox\/docs-agent-runner-recipe\/v1|recipe_path|recipe_json|wp_codebox_ref|datamachine|data machine|data-machine|agents api|sandbox mounts|ability ids|provider internals|homeboy|require_app_token/i)
81+
assert.doesNotMatch(docs.slice(0, docs.indexOf("## Runtime Coverage")), /docs-agent|wp-codebox\/docs-agent-runner-recipe\/v1|recipe_path|recipe_json|wp_codebox_ref|datamachine|data machine|data-machine|agents api|sandbox mounts|ability ids|provider internals|homeboy|require_app_token/i)
6182

6283
const tmp = await mkdtemp(join(tmpdir(), "wp-codebox-agent-task-workflow-"))
6384
const nativePackageRepository = join(tmp, "native-package-repository")

0 commit comments

Comments
 (0)