From 265c8093513f1855a50721307562f56dfadb9f56 Mon Sep 17 00:00:00 2001 From: namastex888 Date: Sat, 25 Jul 2026 13:42:09 -0300 Subject: [PATCH 1/3] fix(release): authenticate the manifest push as a GitHub App MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The manifest job has failed every release since 2026-07-20, freezing the channel pointers: stable at 5.260720.10 and dev at 5.260723.8, while v5.260724.1 published 12 assets that no channel can reach and the v5.260724.2 promotion tag has no release at all. Root cause is not the credential's scope. The 'main' ruleset (id 9203218) carries a pull_request rule with an EMPTY bypass_actors list, so every direct push to main is rejected GH013 no matter how the pusher authenticates. The deploy key failed that way (deploy keys are also disabled org-wide), and the fine-grained PAT that replaced it failed the same way — the preflight passed on 07-24 and the push still 403'd. An App is the narrowest identity that can hold a ruleset bypass; granting it to a RepositoryRole would hand every admin direct push to main and undo the least-privilege posture F16-F18 established. So the job now mints a short-lived App token per run (expires ~1h, nothing long-lived can push to main) and checks out with it. - release-publish.yml: preflight requires RELEASE_MANIFESTS_APP_ID and RELEASE_MANIFESTS_APP_PRIVATE_KEY, mints via create-github-app-token pinned to fee1f7d6 (v3.2.0), checkout uses the minted token; HUMAN SETUP header rewritten with the App + ruleset-bypass provisioning steps and the exact gh api bypass command - push-rejection diagnostic now names the ruleset bypass, not just scope - release.yml / version.yml: secrets-inherit comments follow the rename - release-docs.test.ts: the contract test pinned the PAT model; it now pins the App model and asserts the PAT wiring is gone Requires one-time provisioning before the next release: create the App, store its credentials in the release-manifests environment, and add it as an Integration bypass actor on ruleset 9203218. --- .github/workflows/release-publish.yml | 83 +++++++++++++++++++-------- .github/workflows/release.yml | 6 +- .github/workflows/version.yml | 3 +- scripts/release-docs.test.ts | 17 ++++-- 4 files changed, 76 insertions(+), 33 deletions(-) diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 806dbdd34..ab2dd8889 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -1029,47 +1029,82 @@ jobs: timeout-minutes: 10 environment: # No reviewers: dev/homolog stay automatic. The environment is restricted - # to main and holds the sole credential (RELEASE_MANIFESTS_TOKEN) allowed - # to push the .well-known manifests through main branch protection. + # to main and holds the sole credential (the RELEASE_MANIFESTS App key) + # allowed to push the .well-known manifests through the main ruleset. name: release-manifests permissions: contents: read # ----------------------------------------------------------------------- - # HUMAN SETUP (one-time) — the manifest push authenticates with a - # fine-grained PAT, NOT a deploy key. Deploy keys are DISABLED org-wide - # (repos/automagik-dev/genie/keys POST → 422 "Deploy keys are disabled"), - # so the old ssh-key model silently fell back to github-actions[bot]'s - # token, whose push to protected main 403s. That broke 4 releases. + # HUMAN SETUP (one-time) — the manifest push authenticates as a GitHub + # App, NOT a PAT and NOT a deploy key. + # + # History: deploy keys are DISABLED org-wide (repos/automagik-dev/genie/keys + # POST → 422 "Deploy keys are disabled"), so the ssh-key model silently fell + # back to github-actions[bot], whose push to protected main 403s. Replacing + # it with a fine-grained PAT did not help either: the `main` ruleset + # (id 9203218) carries a `pull_request` rule with an EMPTY bypass_actors + # list, so *every* direct push to main is rejected GH013 regardless of the + # token's contents:write scope. A ruleset bypass is required, and an App is + # the narrowest identity that can hold one — granting it to a RepositoryRole + # would hand every admin direct push to main. # # To provision (repo/org admin): - # 1. Create a FINE-GRAINED PAT scoped to automagik-dev/genie ONLY, with - # Repository permissions → Contents: Read and write (nothing else). - # 2. Store it as the environment secret RELEASE_MANIFESTS_TOKEN in the - # `release-manifests` GitHub environment (Settings → Environments). - # 3. Allow the PAT's identity through main branch protection (add it to - # the bypass/allow list for "Restrict who can push to matching - # branches"), or the push will 403 exactly as the deploy key did. - # 4. Delete the now-unused RELEASE_MANIFESTS_DEPLOY_KEY environment - # secret and any orphaned deploy key. + # 1. Create an org-owned GitHub App (suggested name: genie-release-bot) + # with Repository permissions → Contents: Read and write (nothing + # else). Generate a private key. + # 2. Install it on automagik-dev/genie ONLY (no other repositories). + # 3. Store its credentials as environment secrets in the + # `release-manifests` environment (Settings → Environments): + # RELEASE_MANIFESTS_APP_ID and RELEASE_MANIFESTS_APP_PRIVATE_KEY. + # 4. Add the App as a bypass actor on the `main` ruleset: + # gh api repos/automagik-dev/genie/rulesets/9203218 -X PUT \ + # -f 'bypass_actors[][actor_type=Integration]' \ + # -F 'bypass_actors[][actor_id]=' \ + # -f 'bypass_actors[][bypass_mode]=always' + # Without this the push fails GH013 exactly as the PAT did. + # 5. Delete the now-unused RELEASE_MANIFESTS_TOKEN and any stale + # RELEASE_MANIFESTS_DEPLOY_KEY environment secret. + # + # The App token is minted per run and expires in ~1h, so no long-lived + # push credential to main exists anywhere. # (Kept in the workflow header because docs/ is a submodule; mirror this # into docs/release-process.mdx when that submodule is next updated.) # ----------------------------------------------------------------------- steps: - - name: Preflight — require the manifest push token + - name: Preflight — require the manifest push App credentials env: - RELEASE_MANIFESTS_TOKEN: ${{ secrets.RELEASE_MANIFESTS_TOKEN }} + RELEASE_MANIFESTS_APP_ID: ${{ secrets.RELEASE_MANIFESTS_APP_ID }} + RELEASE_MANIFESTS_APP_PRIVATE_KEY: ${{ secrets.RELEASE_MANIFESTS_APP_PRIVATE_KEY }} shell: bash run: | set -euo pipefail - if [[ -z "${RELEASE_MANIFESTS_TOKEN}" ]]; then - echo "::error::RELEASE_MANIFESTS_TOKEN is not set in the 'release-manifests' environment." >&2 - echo "::error::Without it actions/checkout silently falls back to github-actions[bot], whose push to protected main is rejected 403 (misreported below as a CAS race)." >&2 - echo "::error::Fix: create a fine-grained PAT (contents:read+write on automagik-dev/genie only), store it as the RELEASE_MANIFESTS_TOKEN environment secret, allow it through main branch protection, then delete the stale RELEASE_MANIFESTS_DEPLOY_KEY. See this job's HUMAN SETUP header." >&2 + missing=0 + if [[ -z "${RELEASE_MANIFESTS_APP_ID:-}" ]]; then + echo "::error::RELEASE_MANIFESTS_APP_ID is not set in the 'release-manifests' environment." >&2 + missing=1 + fi + if [[ -z "${RELEASE_MANIFESTS_APP_PRIVATE_KEY:-}" ]]; then + echo "::error::RELEASE_MANIFESTS_APP_PRIVATE_KEY is not set in the 'release-manifests' environment." >&2 + missing=1 + fi + if [[ "${missing}" -ne 0 ]]; then + echo "::error::Without App credentials actions/checkout silently falls back to github-actions[bot], whose push to protected main is rejected (misreported below as a CAS race)." >&2 + echo "::error::Fix: create an org-owned App with contents:read+write on automagik-dev/genie only, store its id/private key as the RELEASE_MANIFESTS_APP_ID and RELEASE_MANIFESTS_APP_PRIVATE_KEY environment secrets, and add the App as an Integration bypass actor on the 'main' ruleset. See this job's HUMAN SETUP header." >&2 exit 1 fi + + - name: Mint a short-lived App token for the manifest push + id: manifest_app_token + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v3.2.0 + with: + app-id: ${{ secrets.RELEASE_MANIFESTS_APP_ID }} + private-key: ${{ secrets.RELEASE_MANIFESTS_APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: - token: ${{ secrets.RELEASE_MANIFESTS_TOKEN }} + token: ${{ steps.manifest_app_token.outputs.token }} persist-credentials: true - name: Download the exact pre-endorsed candidate manifest bytes @@ -1147,7 +1182,7 @@ jobs: fi printf '%s\n' "$push_output" >&2 if printf '%s' "$push_output" | grep -qiE 'protected branch|GH006|permission to .* denied|not authorized|403|forbidden|remote rejected'; then - echo "::error::manifest push to main was REJECTED for lack of permission — this is NOT a concurrent-update CAS race. RELEASE_MANIFESTS_TOKEN lacks contents:write on ${GITHUB_REPOSITORY} or is not allowed through main branch protection. See this job's HUMAN SETUP header." >&2 + echo "::error::manifest push to main was REJECTED for lack of permission — this is NOT a concurrent-update CAS race. Either the RELEASE_MANIFESTS App lacks contents:write on ${GITHUB_REPOSITORY}, or it is not a bypass actor on the 'main' ruleset (an empty bypass_actors list rejects every direct push GH013). See this job's HUMAN SETUP header." >&2 exit 1 fi echo "::warning ::release-manifest.cas_retry attempt ${attempt}/5: non-fast-forward from a concurrent main update; recomputing" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b058cee9..8eca175f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -189,7 +189,7 @@ jobs: source_ci_run_id: ${{ inputs.source_ci_run_id }} draft: false # Without inherit, the called workflow's secrets context is empty and the - # manifests job sees RELEASE_MANIFESTS_TOKEN as unset even though the - # release-manifests environment holds it (proven on the v5.260720.10 - # stable run). Local trusted workflow — inherit is safe. + # manifests job sees RELEASE_MANIFESTS_APP_ID/_PRIVATE_KEY as unset even + # though the release-manifests environment holds them (proven on the + # v5.260720.10 stable run). Local trusted workflow — inherit is safe. secrets: inherit diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 322d677b9..a62967971 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -441,5 +441,6 @@ jobs: source_branch: ${{ needs.auto-version.outputs.source_branch }} source_ci_run_id: ${{ needs.auto-version.outputs.source_ci_run_id }} # Propagate secrets through the call chain so release-publish.yml's - # manifests job can read RELEASE_MANIFESTS_TOKEN (see release.yml publish). + # manifests job can read the RELEASE_MANIFESTS App credentials (see + # release.yml publish). secrets: inherit diff --git a/scripts/release-docs.test.ts b/scripts/release-docs.test.ts index 056e01535..ad8c21d99 100644 --- a/scripts/release-docs.test.ts +++ b/scripts/release-docs.test.ts @@ -480,11 +480,18 @@ describe('Group E release and documentation contracts', () => { expect(workflow).not.toContain('/immutable-releases'); expect(workflow).not.toContain('--clobber'); expect(workflow).toContain('name: release-manifests'); - // Manifest push authenticates with a fine-grained PAT, not a deploy key - // (deploy keys are disabled org-wide; the ssh-key model silently fell back - // to the bot token and 403'd on protected main). The stale deploy-key - // secret must be gone. - expect(workflow).toContain('token: ${{ secrets.RELEASE_MANIFESTS_TOKEN }}'); + // Manifest push authenticates as a GitHub App — not a deploy key (disabled + // org-wide; the ssh-key model silently fell back to the bot token and 403'd + // on protected main) and not a fine-grained PAT (which still cannot push: + // the `main` ruleset carries a pull_request rule with an EMPTY bypass_actors + // list, so every direct push is rejected GH013 regardless of token scope). + // Only an App identity can hold that ruleset bypass, and its token is minted + // per run rather than stored. Both stale credentials must be gone. + expect(workflow).toContain('uses: actions/create-github-app-token@'); + expect(workflow).toContain('app-id: ${{ secrets.RELEASE_MANIFESTS_APP_ID }}'); + expect(workflow).toContain('private-key: ${{ secrets.RELEASE_MANIFESTS_APP_PRIVATE_KEY }}'); + expect(workflow).toContain('token: ${{ steps.manifest_app_token.outputs.token }}'); + expect(workflow).not.toContain('token: ${{ secrets.RELEASE_MANIFESTS_TOKEN }}'); expect(workflow).not.toContain('RELEASE_MANIFESTS_DEPLOY_KEY: ${{'); expect(workflow).not.toContain('ssh-key: ${{ secrets.RELEASE_MANIFESTS_DEPLOY_KEY }}'); expect(workflow).toContain('[release-manifest]'); From 1431cb9c4c74d0a4311da73d4172144defa732e3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Jul 2026 16:55:30 +0000 Subject: [PATCH 2/3] chore(version): bump to 5.260725.1 [auto-version] --- .claude-plugin/marketplace.json | 2 +- package.json | 2 +- plugins/genie/.claude-plugin/plugin.json | 2 +- plugins/genie/.codex-plugin/plugin.json | 2 +- plugins/genie/package.json | 2 +- plugins/hermes-genie/plugin.yaml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 57c7a752a..92851f885 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "plugins": [ { "name": "genie", - "version": "5.260724.1", + "version": "5.260725.1", "source": "./plugins/genie", "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project." } diff --git a/package.json b/package.json index d06acd4f7..e58ba34e7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@automagik/genie", - "version": "5.260724.1", + "version": "5.260725.1", "description": "Collaborative terminal toolkit for human + AI workflows. NOTE: npm distribution discontinued 2026-05-09 — install via `curl -fsSL https://raw.githubusercontent.com/automagik-dev/genie/main/install.sh | bash` (cosign + SLSA verified). See https://automagik.dev/genie/release-process", "license": "MIT", diff --git a/plugins/genie/.claude-plugin/plugin.json b/plugins/genie/.claude-plugin/plugin.json index acb3ea05e..46efa460b 100644 --- a/plugins/genie/.claude-plugin/plugin.json +++ b/plugins/genie/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "genie", - "version": "5.260724.1", + "version": "5.260725.1", "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.", "author": { "name": "Namastex Labs" diff --git a/plugins/genie/.codex-plugin/plugin.json b/plugins/genie/.codex-plugin/plugin.json index 1c4f30830..bb10d8df0 100644 --- a/plugins/genie/.codex-plugin/plugin.json +++ b/plugins/genie/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "genie", - "version": "5.260724.1", + "version": "5.260725.1", "description": "Plan, execute, review, and ship software with Genie workflows in Codex.", "author": { "name": "Namastex Labs", diff --git a/plugins/genie/package.json b/plugins/genie/package.json index 90bf78ec8..9650631b6 100644 --- a/plugins/genie/package.json +++ b/plugins/genie/package.json @@ -1,6 +1,6 @@ { "name": "genie-plugin", - "version": "5.260724.1", + "version": "5.260725.1", "private": true, "description": "Runtime dependencies for genie bundled CLIs", "license": "MIT", diff --git a/plugins/hermes-genie/plugin.yaml b/plugins/hermes-genie/plugin.yaml index b683d4460..60820c42d 100644 --- a/plugins/hermes-genie/plugin.yaml +++ b/plugins/hermes-genie/plugin.yaml @@ -1,5 +1,5 @@ name: genie -version: 5.260724.1 +version: 5.260725.1 description: "Native Hermes surface for Genie orchestration: read-only status plus work-plan and review-plan gap tools that the MCP board surface does not cover, hooks, commands, and a thin cockpit skill. Board/task truth comes from the genie MCP tools." provides_tools: # Default surface: exactly the three gap tools the MCP board surface does not cover. From 8e104c5aa42e13fb6295f9f23721194348ae06ee Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Jul 2026 16:59:53 +0000 Subject: [PATCH 3/3] chore(version): bump to 5.260725.2 [auto-version] --- .claude-plugin/marketplace.json | 2 +- package.json | 2 +- plugins/genie/.claude-plugin/plugin.json | 2 +- plugins/genie/.codex-plugin/plugin.json | 2 +- plugins/genie/package.json | 2 +- plugins/hermes-genie/plugin.yaml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 92851f885..7504476ab 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "plugins": [ { "name": "genie", - "version": "5.260725.1", + "version": "5.260725.2", "source": "./plugins/genie", "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project." } diff --git a/package.json b/package.json index e58ba34e7..601ea3c0c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@automagik/genie", - "version": "5.260725.1", + "version": "5.260725.2", "description": "Collaborative terminal toolkit for human + AI workflows. NOTE: npm distribution discontinued 2026-05-09 — install via `curl -fsSL https://raw.githubusercontent.com/automagik-dev/genie/main/install.sh | bash` (cosign + SLSA verified). See https://automagik.dev/genie/release-process", "license": "MIT", diff --git a/plugins/genie/.claude-plugin/plugin.json b/plugins/genie/.claude-plugin/plugin.json index 46efa460b..5c986d956 100644 --- a/plugins/genie/.claude-plugin/plugin.json +++ b/plugins/genie/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "genie", - "version": "5.260725.1", + "version": "5.260725.2", "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.", "author": { "name": "Namastex Labs" diff --git a/plugins/genie/.codex-plugin/plugin.json b/plugins/genie/.codex-plugin/plugin.json index bb10d8df0..3ea67a4e6 100644 --- a/plugins/genie/.codex-plugin/plugin.json +++ b/plugins/genie/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "genie", - "version": "5.260725.1", + "version": "5.260725.2", "description": "Plan, execute, review, and ship software with Genie workflows in Codex.", "author": { "name": "Namastex Labs", diff --git a/plugins/genie/package.json b/plugins/genie/package.json index 9650631b6..40e911a3c 100644 --- a/plugins/genie/package.json +++ b/plugins/genie/package.json @@ -1,6 +1,6 @@ { "name": "genie-plugin", - "version": "5.260725.1", + "version": "5.260725.2", "private": true, "description": "Runtime dependencies for genie bundled CLIs", "license": "MIT", diff --git a/plugins/hermes-genie/plugin.yaml b/plugins/hermes-genie/plugin.yaml index 60820c42d..1f0956a14 100644 --- a/plugins/hermes-genie/plugin.yaml +++ b/plugins/hermes-genie/plugin.yaml @@ -1,5 +1,5 @@ name: genie -version: 5.260725.1 +version: 5.260725.2 description: "Native Hermes surface for Genie orchestration: read-only status plus work-plan and review-plan gap tools that the MCP board surface does not cover, hooks, commands, and a thin cockpit skill. Board/task truth comes from the genie MCP tools." provides_tools: # Default surface: exactly the three gap tools the MCP board surface does not cover.