Skip to content

Authenticate index generation as a GitHub App - #41

Merged
lwaldron merged 5 commits into
mainfrom
index-deploy-key
Sep 14, 2026
Merged

lwaldron merged 5 commits into
mainfrom
index-deploy-key

Conversation

@lwaldron

@lwaldron lwaldron commented Sep 14, 2026 •

Copy link
Copy Markdown
Member

Index generation on a content repository fails once main is protected: the generator runs, the push is rejected with GH013, because github-actions[bot] is not a bypass actor.

It cannot be made one — bypass actors of type Integration must be GitHub Apps installed on the organisation, and GitHub Actions is not one. The API rejects app id 15368 outright. A purpose-built App is, and that's what the template now uses.

The change

  • template/.github/workflows/generate-index.yml — mints a token with actions/create-github-app-token and passes it to actions/checkout, inside an index-generation environment.
  • actions/generate-index/action.yml — comments only, explaining what the push authenticates with and why it cannot loop.
  • README.md — a new adoption step, since an adopter with a protected branch hits this before their first index ever generates.

The composite action's git push is unchanged: actions/checkout persists whatever credential it was given.

Why an App rather than a deploy key

Both work. The App is better on two counts that matter here.

Ruleset bypass is not per-rule. A DeployKey bypass covers the whole ruleset, so the key could force-push or delete main, not merely skip the pull-request requirement. An App scoped to contents: write cannot.

Static versus expiring. A deploy key is valid until revoked; an installation token lasts an hour.

It also installs once on an organisation rather than once per repository, which matters as soon as a second content node exists.

Why environment secrets rather than repository secrets

A repository secret is readable by any workflow in the repository, including one added in a pull request — secrets are withheld from fork PRs, but same-repo branch PRs do receive them, and these mint a token that bypasses branch protection. The environment's deployment branch policy restricts it to main.

The on: push: branches: [main] trigger already prevents PR execution, so this is belt-and-braces — but it makes the property structural rather than dependent on nobody adding a workflow_dispatch later.

Two smaller things in here

A README sentence I broke in the citation rename. It claimed the starter's method_origin_citation is the placeholder "in the required protocol_citation", which is incoherent — the placeholder is in protocol_citation, and method_origin_citation ships commented out.

Untracking the bytecode caches, in its own commit and separable if you'd rather it weren't here. #39 committed ten .pyc files; they're interpreter-keyed, so running the suite locally on 3.14 produced ten more beside the committed 3.12 ones.

77 tests pass.

🤖 Generated with Claude Code

lwaldron and others added 2 commits September 13, 2026 21:52
Index generation has been failing since `require-pr-review` was added to
`agent-protocols`. The generator runs fine; the push is rejected with GH013,
because `github-actions[bot]` is not a bypass actor.

It cannot be made one. Bypass actors of type Integration must be GitHub Apps
installed on the organisation, and GitHub Actions is not one — the API rejects
app id 15368 outright. A write-scoped deploy key is a valid bypass actor type
and is scoped to the single repository.

`actions/checkout` takes an `ssh-key` input, loads the key, and sets the remote
to SSH, so the composite action's existing `git push` needs no change. Only the
caller's checkout does.

Documented in the template workflow, in the action's push step, and as an
adoption step in the README, since an adopter with a protected branch hits this
before their first index generates.

Two notes worth having next to the code. A deploy-key push *does* trigger
workflows where a GITHUB_TOKEN push does not, so the loop protection matters
now: the index commit is outside the caller's `paths:` filter, and the default
commit message carries [skip ci] besides.

Also corrects a sentence I broke in the citation rename. The README claimed the
starter's `method_origin_citation` is the placeholder "in the required
`protocol_citation`", which is incoherent — the placeholder is in
`protocol_citation`, and `method_origin_citation` ships commented out.

77 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR #39 committed ten `.pyc` files. They are build artifacts keyed to the
interpreter version, so every contributor on a different Python adds their own
set: running the test suite locally on 3.14 produced ten more alongside the
committed 3.12 ones, and `git add -A` swept them into a commit before I noticed.

Untracked and gitignored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 14, 2026 01:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Scope the loop-protection guarantee to the template workflow and address equivalent safeguards for other callers.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Updates index generation to use a deploy key for protected branches, documents setup, corrects citation guidance, and ignores Python bytecode caches.

Changes:

  • Passes INDEX_DEPLOY_KEY to checkout.
  • Documents deploy-key configuration and loop safeguards.
  • Corrects citation wording and adds Python cache exclusions.
File summaries
File Description
template/.github/workflows/generate-index.yml Configures SSH deploy-key checkout.
README.md Adds adoption instructions and citation correction.
actions/generate-index/action.yml Documents push authentication and loop protection.
.gitignore Ignores Python bytecode artifacts.
Review details
  • Files reviewed: 3/14 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread actions/generate-index/action.yml Outdated
A repository secret is readable by any workflow in the repository, including
one added in a pull request — secrets are withheld from fork pull requests, but
same-repo branch pull requests do receive them. With five contributors about to
get write access and a key that bypasses branch protection, that is the wrong
place for it.

An environment secret whose deployment branch policy allows only `main` closes
it: a job can read the secret only if it declares the environment and is running
on `main`. The `on: push: branches: [main]` trigger already prevents pull-request
execution today, so this makes the property structural rather than dependent on
nobody adding a workflow_dispatch later.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A deploy key is a static credential valid until someone revokes it, and ruleset
bypass applies to the whole ruleset — so the key could force-push or delete
`main`, not merely skip the pull-request requirement.

A purpose-built App holds `contents: write` and nothing else, mints a token that
expires after an hour, and installs once on the organisation rather than once per
repository, which matters as soon as there is a second content node.

GitHub's own deploy-key page recommends this; I offered four options earlier and
an App was not among them, which was the gap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lwaldron lwaldron changed the title Push the index with a deploy key, not GITHUB_TOKEN Authenticate index generation as a GitHub App Sep 14, 2026
@lwaldron
lwaldron requested a lite review from Copilot September 14, 2026 02:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved workflow authentication and recursion-documentation issues must be addressed before approval.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (2)

actions/generate-index/action.yml:50

  • The loop-prevention explanation has the same stale assumption: the template's App-token push, like a deploy-key push, does trigger workflows, whereas a GITHUB_TOKEN push does not. Please make this statement cover both non-GITHUB_TOKEN credentials so it accurately explains why the new template is safe.
    # This cannot loop. A deploy-key push does trigger workflows (a GITHUB_TOKEN push does not), but
    # the commit touches only the index, which is outside the caller's `paths:` filter, and the
    # default commit message carries [skip ci] as well.

actions/generate-index/action.yml:50

  • generate_protocols_yaml.py writes a fresh generated_at timestamp on every run, while this action lets callers change both inputs.output and inputs.commit-message. A caller that includes that output in its push-path filter and overrides the message without [skip ci] will retrigger the action and create another changed index, so “This cannot loop” is only true for the template's current filters and default. Qualify the claim or enforce a recursion guard in the action.
    # This cannot loop. A deploy-key push does trigger workflows (a GITHUB_TOKEN push does not), but
    # the commit touches only the index, which is outside the caller's `paths:` filter, and the
    # default commit message carries [skip ci] as well.
  • Files reviewed: 3/14 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread template/.github/workflows/generate-index.yml
Comment thread actions/generate-index/action.yml Outdated
The template ran `create-github-app-token` unconditionally while the README told
adopters with an unprotected `main` to skip creating the App. Both inputs would
be empty, the step would fail before checkout, and the first index would never
generate. It is now skipped when no App is configured and falls back to
GITHUB_TOKEN, so the template works either way. `secrets` is not available to a
step `if:`, hence the job-level `env`.

`permission-contents: write` is now requested explicitly rather than inheriting
whatever the installation happens to hold, so granting the App another permission
later cannot silently widen the token.

GITHUB_TOKEN drops to `contents: read` in the content repository, where the App
always pushes. The template keeps `contents: write`, because its fallback path
pushes with GITHUB_TOKEN — reducing it there would have broken the case the
fallback exists for. The comment says which is which.

The action's comment still described a deploy key and `ssh-key` after the switch
to an App token, and claimed the push "cannot loop". Neither was true of the
action: loop protection lives in the template's `paths:` filter and `[skip ci]`
default, and a caller with neither will loop. Both corrected, with the guarantee
scoped to the caller.

Commit attribution is now an input rather than a hardcoded github-actions[bot],
defaulting to it so existing callers are unaffected. The workflows pass the App's
own identity, since that is what pushes.

Declined, deliberately: pinning generate-index@v0 to a commit SHA, and protecting
tags on this repository. The escalation path needs write access to this repo,
which is the same set of people who already have write access to the content
repo, and pinning would give up the moving-tag design ADR 0006 §3 argues for.

77 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lwaldron
lwaldron merged commit 67e31bf into main Sep 14, 2026
@lwaldron
lwaldron deleted the index-deploy-key branch September 14, 2026 02:34
@lwaldron
lwaldron requested a lite review from Copilot September 14, 2026 03:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Address the critical shell-injection risk and the moderate least-privilege documentation issue.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 3/14 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment on lines +70 to +71
git config --local user.name "${{ inputs.committer-name }}"
git config --local user.email "${{ inputs.committer-email }}"
Comment thread README.md
Comment on lines +70 to +71
configured. The template's workflow already declares the environment and passes both to
`actions/create-github-app-token`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants