Skip to content

fix(pr-review): API-fallback collaborator check fails with github.token — cannot enumerate collaborators without push #255

@cbeaulieu-gt

Description

@cbeaulieu-gt

Symptom

The API-fallback added in #253 (PR #254) does not work in practice. On glitchwerks/mom-bot#21 (run 25603534852), the new auth code ran but blocked the PR with:

Not authorized: cbeaulieu-gt (association=CONTRIBUTOR, not in authorized_users, not a repo collaborator)

…even though cbeaulieu-gt is repo admin on mom-bot.

Root cause

gh api repos/{owner}/{repo}/collaborators/{username} requires the calling identity to have push access to enumerate collaborators. This is intentional GitHub security behavior (prevents outside contributors from listing collaborators).

In pull_request_target runs, github.token resolves to github-actions[bot] with whatever scopes are declared in the workflow's permissions: block. Even with contents: read and pull-requests: write, the bot identity does not have the push relationship to the repo that the collaborators endpoint requires — it returns 403 (which gh api surfaces as a non-zero exit). The code falls through to "not a collaborator" and blocks.

Reproducible from a local PAT: gh api repos/glitchwerks/mom-bot/collaborators/cbeaulieu-gt exits 0 (because the PAT has admin). The same call inside the workflow exits non-zero.

Why it shipped

The implementation was tested only against the CSV-input path (step 1 of three). The API fallback (step 3) was not exercised end-to-end before merge — and the code reviewer's review was scoped to logic correctness, not GitHub auth-model correctness. Today's session also documented (PR #486 in claude-configs) the rule that "CI green is not dogfood evidence" — and indeed this PR's own CI was green only because the dogfood ran the OLD @v2 code at the time.

Fix options

  1. Use the App token for the collaborator check. The App's installation has admin rights on its installed repos, so the API call succeeds. Requires reordering: resolve App token → run auth check using App token → continue. Chicken-and-egg concern is resolved by the fact that App token resolution is itself bounded by the repo's installation scope (the App is either installed or it's not).
  2. Use actions/github-script with a custom octokit setup using the App-token credentials — same idea as (1) but more idiomatic in JS.
  3. Drop the API fallback entirely. Document the authorized_users allowlist as the canonical solution for private-org-membership cases, and revert step 3. This is the cheapest path; users with private membership add a one-line input to their caller workflow.
  4. Hybrid: keep step 1 + step 2; replace step 3 with a different observable that github.token CAN read (e.g. the commits/{sha}/check-suites endpoint or users/{username} to fetch type: User). Likely cannot fully distinguish collaborator from non-collaborator without push access.

Recommended: (1) — reorder to use App token. The App token is now mandatory after #250 anyway; using it for the auth check is the same security model as everything downstream.

Stopgap: (3) — document authorized_users. Easier to ship today; users add authorized_users: cbeaulieu-gt to their caller workflow. The API-fallback step can stay dormant (it never fires for users in the allowlist or with public membership).

Acceptance

  • PRs by actual collaborators with private org membership are authorized without manual authorized_users configuration. (Strong — option 1.)
  • OR: README documents authorized_users as the required pattern for private membership; the API fallback is removed or noted as ineffective. (Weak — option 3.)
  • No silent-authorization regression on token errors.
  • Verified on a real consumer (glitchwerks/mom-bot) — the verification rule from claude-configs#486 applies; do not trust dogfood-on-self.

Related

🤖 Generated by Claude Code on behalf of @cbeaulieu-gt

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingci

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions