Skip to content

Scope a fine-grained PAT for the gh CLI (Token B) #698

Description

@ppat

Goal

Scope a fine-grained GitHub PAT ("Token B") for the gh CLI that can do everything the GitHub MCP server's token
(Token A, ppat/homelab-ops-kubernetes-apps#3423) can read/write, plus: push to non-default branches including
force-push, create remote branches, create PRs (but never land them), trigger workflow_dispatch, and watch/fetch
run status, logs, and artifacts. This issue exists to preserve that research so it's never redone — it does not
itself change any token; the gh CLI keeps using its current classic PAT until this is acted on.

Proposed permissions

Permission Level Why
Contents Read and write Push commits, create/force-push branches
Pull requests Read and write gh pr create
Actions Read and write Write needed for workflow_dispatch; Read alone would suffice for run status/logs/artifacts
Workflows Read and write Required to push commits that touch .github/workflows/** — something we do routinely
Metadata, Contents (read), Issues, Commit statuses, code/Dependabot/secret-scanning/security-advisory reads Read Same read set as Token A

Hard limits of the fine-grained PAT model

These aren't implementation details to work around later — they're the reason this token needs a server-side
backstop before it's safe to create:

  • "Push to branches but never main" is not expressible in any PAT. Contents: write is all-or-nothing across
    every branch in the repo. The only way to stop a push to main is branch protection / a repository ruleset on
    the GitHub side.
  • "Create a PR but never merge it" is not expressible either — and the reason is worse than it first looks.
    PR merge is gated by Contents: write (not Pull requests), and this token needs Contents: write anyway
    for pushing. So the merge button is already unlocked by a permission this token can't do without. Only a
    server-side rule can stop merges — and since the token authenticates as the same identity as the human account,
    there's no actor-level distinction GitHub can make between "the human clicked merge" and "a script using this
    token clicked merge."
  • User-owned Projects v2 and the notifications API are classic-PAT-only (same finding as Token A) — anything
    depending on them cannot move to a fine-grained token.
  • Use the GH_TOKEN env var, not gh auth login --with-token, per GitHub's own guidance for fine-grained
    tokens.

The Checks API limit is narrower than GitHub documents

Corrected 2026-07-28 by testing Token A directly, and it changes the migration cost:

gh pr checks (and any Checks API call) works on public repos — the baseline read-only public access every
fine-grained PAT carries is sufficient. It 403s only on private repos (Resource not accessible by personal access token, confirmed against homelab-ops-kubernetes-experiments). GitHub's GA "known gaps" list and its
support replies state the limitation unconditionally; in practice it is private-repo-only.

So for the public repos where most work happens, gh pr checks keeps working under Token B. On private repos,
fall back to gh run list --commit <sha> and gh run view --log, which use the Actions API and are unaffected.

What it replaces

The current classic PAT holds repo, workflow, write:packages, admin:public_key, gist, project, user, notifications, read:org, write:discussion — full read/write to every repo the account can see. Migrating to
Token B means losing Projects v2 and notifications access via gh (both classic-PAT-only, per above), and
Checks API access on private repos only — the main costs to weigh against the migration's security benefit.

Prerequisite

None of this is safe to create until direct pushes to main are blocked with "Require a pull request before merging" along with "Restrict who can push to matching branches". Additionally, within branch protection settings for main, configure merge restrictions so that the specific user or token role cannot merge. Ensure you do not add them to the "Bypass list" or "Allow specified actors to bypass" merge requirements, and restrict merge rights strictly to repository admins.

Not yet applied. As of 2026-07-28 both homelab-ops-kubernetes-apps and homelab-ops-kubernetes-clusters
still allow direct pushes to main (branch protection exists but sets no required_pull_request_reviews and no
push restrictions).

Cross-reference

Originates from, and shares its read-permission set and Projects/notifications findings with,
ppat/homelab-ops-kubernetes-apps#3423 (the GitHub MCP server's Token A).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions