diff --git a/.claude/agent-memory/archgate-developer/MEMORY.md b/.claude/agent-memory/archgate-developer/MEMORY.md index 0fc9c5bf..062a3d20 100644 --- a/.claude/agent-memory/archgate-developer/MEMORY.md +++ b/.claude/agent-memory/archgate-developer/MEMORY.md @@ -42,6 +42,7 @@ Skipping steps 2 or 3 is a workflow violation. The user should NEVER have to inv - **`Bun.Glob.scan({ dot: false })` silently drops dot-prefixed segments — even on explicit paths** — `dot: false` (the default) skips matches whose path contains a `.`-prefixed segment, including patterns that explicitly name the dir (e.g. `.github/workflows/release.yml`). Behavior also varies across platforms — Windows reliably drops the match while Linux can match the same pattern, so a rule appears to "work in CI" but no-ops locally. For code repos where `.github/`, `.husky/`, `.vscode/` are first-class source dirs, ALWAYS pass `dot: true` to `Bun.Glob.scan()`. Applied in `src/engine/runner.ts` (`ctx.glob`, `ctx.grepFiles`) and `src/engine/git-files.ts` (`resolveScopedFiles`). See archgate/cli#222. - **PowerShell 5.1 reads BOM-less `.ps1` files as ANSI — never use non-ASCII chars in `install.ps1`** — `install.ps1` has no UTF-8 BOM (first bytes are `# A...`). On Windows PowerShell 5.1, this means the file is decoded as the system codepage (Windows-1252), so multi-byte UTF-8 characters like em-dash (`—`, `\xE2\x80\x94`) get split into garbage bytes that break later string parsing — the parser reports cryptic errors like "string is missing the terminator" on lines that look fine. ALWAYS stick to ASCII (`-`, `--`, straight quotes) in comments and string literals in `install.ps1`. To verify after editing: `[System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path .\install.ps1).Path, [ref]$null, [ref]$errs)`. Same caveat applies to any unsigned `.ps1` file the project distributes. - **SLSA reusable workflow MUST be tag-pinned, not SHA-pinned** — `slsa-framework/slsa-github-generator/.github/workflows/*` looks like it should follow CI-001 (SHA pin), but the SLSA generator's `generate-builder.sh` reads the workflow ref to download the prebuilt builder from a GitHub release and rejects non-tag refs (`Invalid ref: ... Expected ref of the form refs/tags/vX.Y.Z`, exit 2). Pinning by SHA broke the v0.31.0 release ([run 25107195589](https://github.com/archgate/cli/actions/runs/25107195589)). The CI-001 rule allowlists this path so it does NOT block a SHA repin — meaning a future agent could "fix" the tag pin and the rule would be silent until the next release fails. ALWAYS keep `@v2.x.y` for `release-binaries.yml:165` and read the inline comment + CI-001 "Carved-out exceptions" before changing. Upstream issue: [slsa-framework/slsa-github-generator#150](https://github.com/slsa-framework/slsa-github-generator/issues/150). +- **`GITHUB_TOKEN`-authored pushes do NOT trigger downstream workflows — release.yml MUST use the GH App token** — When an Actions workflow pushes commits or opens PRs using `${{ github.token }}` / `secrets.GITHUB_TOKEN`, GitHub intentionally suppresses the resulting `push` / `pull_request` events to prevent recursion. Symptom on release PRs: the head SHA has no `pull_request`-event check runs, so `Validate Code` / `Lint, Test & Check` / `DCO Sign-off Check` are missing from the PR rollup and branch protection treats the PR as missing required checks. PR [#131](https://github.com/archgate/cli/pull/131) papered over this by manually `gh workflow run` + posting commit statuses, but `workflow_dispatch` runs land on `head_branch: release` with `pull_requests: []` — they are not associated with the PR ref, so `Lint, Test & Check` stayed orphaned and the bug recurred on PR [#251](https://github.com/archgate/cli/pull/251). Root-cause fix: in `release.yml` the `pull-request` job MUST generate a GitHub App installation token via `actions/create-github-app-token` (using `secrets.GH_APP_APP_ID` / `secrets.GH_APP_PRIVATE_KEY`) and pass it to BOTH `actions/checkout` and `simple-release-action`. App-token-authored pushes DO trigger `pull_request` events naturally. Apply the same pattern to any future workflow that pushes to a branch whose downstream CI must run. ## Validation Pipeline diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7919acfc..e830be24 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,15 +62,28 @@ jobs: timeout-minutes: 10 name: Pull request permissions: - actions: write - contents: write - pull-requests: write - statuses: write + contents: read needs: check if: needs.check.outputs.workflow == 'pull-request' steps: + # Use a GitHub App installation token (not GITHUB_TOKEN) so the push to the + # release branch triggers downstream `pull_request` events on the release PR. + # GITHUB_TOKEN-authored pushes are intentionally muted by GitHub to prevent + # workflow recursion — that mute is what causes release PRs to land without + # the required `Validate Code` / `Lint, Test & Check` / `DCO Sign-off Check` + # status checks attached to the PR ref. With an App token, the synchronize + # event fires naturally and code-pull-request.yml + dco.yml run against + # refs/pull/N/head, producing checks that branch protection accepts. + - name: Generate token + id: generate_token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3 + with: + app-id: ${{ secrets.GH_APP_APP_ID }} + private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + token: ${{ steps.generate_token.outputs.token }} - uses: moonrepo/setup-toolchain@261c62cb5b0f580c7be7c8cd0f023a2e96756095 # v0 with: auto-install: true @@ -102,46 +115,8 @@ jobs: uses: TrigenSoftware/simple-release-action@a796225481a62b5b46b37d4481ef7fb114a6d399 # v1 with: workflow: pull-request - github-token: ${{ github.token }} + github-token: ${{ steps.generate_token.outputs.token }} branch: release - - name: Run CI and set status checks on release PR - env: - GH_TOKEN: ${{ github.token }} - run: | - HEAD_SHA=$(gh api repos/${{ github.repository }}/git/ref/heads/release --jq '.object.sha' 2>/dev/null) || true - - if [ -z "$HEAD_SHA" ]; then - echo "No release branch found — nothing to validate" - exit 0 - fi - - # Verify DCO sign-off and set status - COMMIT_MSG=$(gh api repos/${{ github.repository }}/git/commits/$HEAD_SHA --jq '.message') - if echo "$COMMIT_MSG" | grep -qiE '^Signed-off-by: .+ <.+>'; then - DCO_STATE=success - DCO_DESC="All commits are signed off" - else - DCO_STATE=failure - DCO_DESC="Missing DCO Signed-off-by line" - fi - - gh api repos/${{ github.repository }}/statuses/$HEAD_SHA \ - -f state="$DCO_STATE" \ - -f context="DCO Sign-off Check" \ - -f description="$DCO_DESC" \ - --silent - - gh workflow run code-pull-request.yml --ref release - sleep 5 - RUN_ID=$(gh run list --branch release --workflow code-pull-request.yml --limit 1 --json databaseId --jq '.[0].databaseId') - echo "Waiting for CI run $RUN_ID..." - gh run watch "$RUN_ID" --exit-status && STATUS=success || STATUS=failure - - gh api repos/${{ github.repository }}/statuses/$HEAD_SHA \ - -f state="$STATUS" \ - -f context="Validate Code" \ - -f description="CI $STATUS" \ - --silent release: runs-on: ubuntu-latest timeout-minutes: 10