Skip to content

fix(github): org repo listing fails with fine-grained PATs lacking Issues:Read#415

Merged
SUSTAPLE117 merged 1 commit into
mainfrom
fix/fgpat-issues-field-forbidden
Apr 6, 2026
Merged

fix(github): org repo listing fails with fine-grained PATs lacking Issues:Read#415
SUSTAPLE117 merged 1 commit into
mainfrom
fix/fgpat-issues-field-forbidden

Conversation

@fproulx-boostsecurity

@fproulx-boostsecurity fproulx-boostsecurity commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Problem

analyze_org fails entirely when using a fine-grained PAT (FGPAT) that doesn't have Issues: Read permission:

ERROR | failed to fetch batch of repos, skipping batch  error="Resource not accessible by personal access token"
ERROR | failed to fetch batch of repos, skipping batch  error="Resource not accessible by personal access token"
ERROR | too many consecutive batch failures, stopping pagination

Root cause: issues { totalCount } is typed IssueConnection! (non-null) in GitHub's GraphQL schema. When a FGPAT lacks Issues: Read on a private repo, GitHub cannot resolve that non-null field and nulls out the entire repo node — not just the issues field. This silently drops private repos from the batch and surfaces as a full batch failure after 3 retries.

Fix

Moves issues fetching into a dedicated issueNodes: repositories(...) GraphQL alias within the same query. The main repositories selection no longer includes issues, so all repos are always returned regardless of Issues: Read permission.

The shurcooL/graphql library unmarshals response data before returning errors. So when issueNodes gets a FORBIDDEN response, Repositories.Nodes is already fully populated — we detect this partial-success case via isIssuesPermissionError() and continue normally. Repos where Issues: Read is missing get openIssuesCount = 0 silently via repoWithIssueCount.

Also adds isFatalError() so true auth failures (bad credentials, 401/403) stop pagination immediately instead of burning through retries.

Minimum required FGPAT permissions

Permission Required
Resource owner = target org
Repository > Contents: Read
Organization > Members: Read ❌ not needed
Repository > Issues: Read ❌ not needed (gracefully degraded)

Test plan

  • analyze_org <org> with a FGPAT scoped to the org with only Contents: Read — all repos (including private) should be listed and scanned, with openIssuesCount: 0
  • analyze_org <org> with a FGPAT that also has Issues: ReadopenIssuesCount should be populated correctly for all repos
  • analyze_org <org> with an invalid/expired token — should log a single fatal error and stop immediately, not retry 3 times

@fproulx-boostsecurity fproulx-boostsecurity requested a review from a team as a code owner April 4, 2026 09:03
@fproulx-boostsecurity fproulx-boostsecurity force-pushed the fix/fgpat-issues-field-forbidden branch from 59ef410 to a5c4075 Compare April 4, 2026 09:20
@fproulx-boostsecurity fproulx-boostsecurity changed the title fix: remove issues.totalCount from org repos GraphQL query fix: org repo listing fails with fine-grained PATs lacking Issues:Read Apr 4, 2026
@fproulx-boostsecurity fproulx-boostsecurity changed the title fix: org repo listing fails with fine-grained PATs lacking Issues:Read fix(github): org repo listing fails with fine-grained PATs lacking Issues:Read Apr 4, 2026
@fproulx-boostsecurity fproulx-boostsecurity force-pushed the fix/fgpat-issues-field-forbidden branch from a5c4075 to 445ac10 Compare April 4, 2026 09:27
…sues:Read

Root cause: `issues { totalCount }` is typed `Int!` in GitHub's GraphQL schema.
When a FGPAT lacks Issues:Read on private repos, GitHub cannot resolve that
non-null field and nulls out the entire repo node — silently dropping those
repos from the batch and surfacing the error as a full batch failure.

Fix: move issues fetching into a dedicated `issueNodes: repositories(...)`
alias within the same GraphQL query. The main `repositories` selection no
longer includes `issues`, so all repos are always returned regardless of
Issues:Read permission. The shurcooL/graphql library unmarshals response data
before returning errors, so when issueNodes gets FORBIDDEN the main repo list
is already intact.

- isIssuesPermissionError() detects this partial-success case so the retry
  function returns nil and pagination continues normally
- repoWithIssueCount carries the count matched by databaseId; repos where
  Issues:Read is missing get count 0 without any error or dropped data
- isFatalError() + early break added so true auth failures (bad credentials,
  401/403) stop pagination immediately instead of exhausting retries

Minimum required FGPAT permissions for analyze_org: resource owner set to the
target org + Repository > Contents: Read. Members:Read and Issues:Read are not
required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@fproulx-boostsecurity fproulx-boostsecurity force-pushed the fix/fgpat-issues-field-forbidden branch from 445ac10 to ca62d12 Compare April 5, 2026 00:51
@SUSTAPLE117 SUSTAPLE117 merged commit 984640c into main Apr 6, 2026
11 checks passed
@SUSTAPLE117 SUSTAPLE117 deleted the fix/fgpat-issues-field-forbidden branch April 6, 2026 12:56
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