fix(ci): authenticate skill updater pull requests (#3404)

Punchcard-Session: amber-orchard-valley-s4
This commit is contained in:
Vincent Koc
2026-08-05 10:24:35 +08:00
committed by GitHub
parent 98a6e04e39
commit e590103d70
3 changed files with 124 additions and 2 deletions
+38 -2
View File
@@ -7,7 +7,6 @@ on:
permissions:
contents: write
pull-requests: write
concurrency:
group: update-skills
@@ -99,11 +98,13 @@ jobs:
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Open or update pull request
- name: Commit and push update branch
if: steps.changes.outputs.changed == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
branch="automation/update-skills"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
@@ -112,6 +113,41 @@ jobs:
git commit -m "chore: update skills"
git push --force-with-lease origin "$branch"
- uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
id: app-token
continue-on-error: true
if: steps.changes.outputs.changed == 'true'
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-pull-requests: write
- uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
id: app-token-fallback
continue-on-error: true
if: steps.changes.outputs.changed == 'true' && steps.app-token.outcome == 'failure'
with:
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-pull-requests: write
- name: Open or update pull request
if: steps.changes.outputs.changed == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
run: |
set -euo pipefail
if [[ -z "${GH_TOKEN:-}" ]]; then
echo "::error::Unable to create a Barnacle GitHub App token. Check the primary GH_APP_PRIVATE_KEY and fallback GH_APP_PRIVATE_KEY_FALLBACK credentials and their pull-request permissions." >&2
exit 1
fi
branch="automation/update-skills"
body="$RUNNER_TEMP/update-skills-pr.md"
{
echo "## Summary"
+1
View File
@@ -9,6 +9,7 @@
### Fixes
- CI: authenticate the scheduled project-skill updater's pull request mutations with the Barnacle GitHub App while retaining the workflow token for provenance lookup and branch publication.
- Workers: materialize zero-byte directory markers without colliding with descendant files, while retaining real empty files and verifying every downloaded artifact digest.
- Deploy: allow an explicitly confirmed backend-only deploy to pause and reliably restore active external-skill rollouts instead of requiring a manual dashboard toggle.
- GitHub Actions/CLI: trigger exact pre-publication checks immediately and wait for package publication to finish, so a pending staged upload no longer reports a successful release.
+85
View File
@@ -0,0 +1,85 @@
/* @vitest-environment node */
import { readFile } from "node:fs/promises";
import { describe, expect, it } from "vitest";
import { parse as parseYaml } from "yaml";
type WorkflowStep = {
"continue-on-error"?: boolean;
env?: Record<string, unknown>;
id?: string;
if?: string;
name?: string;
run?: string;
uses?: string;
with?: Record<string, unknown>;
};
describe("update skills workflow", () => {
it("uses GitHub App auth only for pull request mutations", async () => {
const workflow = parseYaml(await readFile(".github/workflows/update-skills.yml", "utf8")) as {
jobs: {
update: {
steps: WorkflowStep[];
};
};
permissions?: Record<string, string>;
};
expect(workflow.permissions).toEqual({ contents: "write" });
const steps = workflow.jobs.update.steps;
const provenanceStep = steps.find((step) => step.name === "Refresh changed skill provenance");
expect(provenanceStep?.env).toEqual({ GH_TOKEN: "${{ github.token }}" });
const pushStep = steps.find((step) => step.name === "Commit and push update branch");
expect(pushStep?.env).toEqual({ GH_TOKEN: "${{ github.token }}" });
expect(pushStep?.run).toContain('git push --force-with-lease origin "$branch"');
expect(pushStep?.run).not.toContain("gh pr ");
const primaryTokenStep = steps.find((step) => step.id === "app-token");
expect(primaryTokenStep).toMatchObject({
uses: "actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3",
"continue-on-error": true,
if: "steps.changes.outputs.changed == 'true'",
with: {
"app-id": "2729701",
"private-key": "${{ secrets.GH_APP_PRIVATE_KEY }}",
owner: "${{ github.repository_owner }}",
repositories: "${{ github.event.repository.name }}",
"permission-pull-requests": "write",
},
});
const fallbackTokenStep = steps.find((step) => step.id === "app-token-fallback");
expect(fallbackTokenStep).toMatchObject({
uses: "actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3",
"continue-on-error": true,
if: "steps.changes.outputs.changed == 'true' && steps.app-token.outcome == 'failure'",
with: {
"app-id": "2971289",
"private-key": "${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}",
owner: "${{ github.repository_owner }}",
repositories: "${{ github.event.repository.name }}",
"permission-pull-requests": "write",
},
});
const pullRequestStep = steps.find((step) => step.name === "Open or update pull request");
expect(pullRequestStep?.env).toEqual({
GH_TOKEN: "${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}",
});
const pullRequestRun = pullRequestStep?.run ?? "";
expect(pullRequestRun).toContain("gh pr list");
expect(pullRequestRun).toContain("gh pr edit");
expect(pullRequestRun).toContain("gh pr create");
expect(pullRequestRun).not.toContain("git push");
expect(JSON.stringify(pullRequestStep)).not.toContain("github.token");
const tokenGuard = 'if [[ -z "${GH_TOKEN:-}" ]]';
expect(pullRequestRun).toContain(tokenGuard);
expect(pullRequestRun).toContain("primary GH_APP_PRIVATE_KEY");
expect(pullRequestRun).toContain("fallback GH_APP_PRIVATE_KEY_FALLBACK");
expect(pullRequestRun.indexOf(tokenGuard)).toBeLessThan(pullRequestRun.indexOf("gh pr list"));
});
});