repomatic.github.token module

GitHub token validation utilities.

Provides early validation for CLI commands that depend on the GitHub API, so users get clear error messages at startup rather than opaque failures mid-execution.

Note

Why REPOMATIC_PAT is needed

GitHub’s GITHUB_TOKEN cannot modify workflow files in .github/. Neither contents: write, actions: write, nor permissions: write-all grant this ability. The only way to push changes to workflow YAML files is via a fine-grained Personal Access Token with the Workflows permission. Without it, pushes are rejected with:

! [remote rejected] branch_xxx -> branch_xxx (refusing to allow a
GitHub App to create or update workflow
``.github/workflows/my_workflow.yaml`` without ``workflows`` permission)

Additionally, events triggered by GITHUB_TOKEN do not start new workflow runs (see GitHub docs), so tag pushes also need the PAT to trigger downstream workflows.

The Settings → Actions → General → Workflow permissions setting has no effect on this limitation — it’s a hard security boundary enforced by GitHub regardless of repository-level settings.

The permission has to reach the actual git push, not just the gh CLI: setting GH_TOKEN in a step’s env only authenticates gh/repomatic API calls made by that process. A bare git push (git_ops.force_push_branch, which every pr-sync template goes through) instead authenticates with whatever credentials actions/checkout configured for the job, which defaults to github.token regardless of GH_TOKEN. A job whose diff can touch .github/workflows/ needs token: ${{ secrets.REPOMATIC_PAT || github.token }} on its own checkout step too, or the push is rejected exactly as if the PAT had never been set.

Jobs that use REPOMATIC_PAT:

  • autofix.yaml: fix-typos, sync-repomatic, sync-action-pins, sync-workflow-pins (PRs touching .github/workflows/ files), sync-tool-versions (upstream-only dependency PRs), fix-vulnerable-deps (reads the GitHub Advisory Database).

  • changelog.yaml: prepare-release (freezes versions in workflow files).

  • release.yaml: create-tag (push triggers on.push.tags), create-release (triggers downstream workflows).

All jobs fall back to GITHUB_TOKEN when the PAT is unavailable (secrets.REPOMATIC_PAT || github.token), but operations requiring the workflows permission or workflow triggering will silently fail.

Token permission mapping:

  • Workflows — PRs that touch .github/workflows/ files.

  • Contents — Tag pushes, release publishing, PR branch creation.

  • Pull requests — All PR-creating jobs.

  • Dependabot alerts — fix-vulnerable-deps reads vulnerability alerts.

  • Issues — Setup guide issue.

  • Administration — Reads the Actions settings the setup guide verifies: SHA pinning required, and the fork-PR contributor-approval policy. Read-only: repomatic never writes a repository setting.

  • Metadata — Required for all fine-grained token API operations.

repomatic.github.token.require_token(module, attr)[source]

Decorator that runs a token validator before the Click command body.

Uses late-bound getattr(module, attr) so that unittest.mock.patch can replace the module attribute after import and the decorator sees the mock at call time.

Return type:

Callable[[Callable[..., Any]], Callable[..., Any]]

class repomatic.github.token.PatProbe(field: str, permission: str, endpoint: str, success: str, not_found: str = '')[source]

Bases: NamedTuple

One fine-grained PAT permission probe.

A read-only API call whose HTTP 403 unambiguously identifies the missing fine-grained permission. Rows live in PAT_PERMISSION_PROBES.

Create new instance of PatProbe(field, permission, endpoint, success, not_found)

field: str

The PatPermissionResults field receiving this probe’s result.

permission: str

Fine-grained permission label, as GitHub’s PAT form spells it.

endpoint: str

Read-only probe endpoint, with a {repo} placeholder.

success: str

Message reported when the probe returns a 2xx.

not_found: str

Message template (with {repo}) when the probe 404s.

Empty for probes whose 404 carries no special meaning: those fall through to the generic failure classification.

repomatic.github.token.PAT_PERMISSION_PROBES: tuple[PatProbe, ...] = (('administration', 'Administration: Read-only', 'repos/{repo}/actions/permissions', 'Administration: token has access', ''), ('contents', 'Contents: Read and Write', 'repos/{repo}/contents/.github', 'Contents: token has access', ''), ('issues', 'Issues: Read and Write', 'repos/{repo}/issues?per_page=1&state=all', 'Issues: token has access', ''), ('pull_requests', 'Pull requests: Read and Write', 'repos/{repo}/pulls?per_page=1&state=all', 'Pull requests: token has access', ''), ('vulnerability_alerts', 'Dependabot alerts: Read-only', 'repos/{repo}/dependabot/alerts?per_page=1', 'Dependabot alerts: token has access, alerts enabled', 'Vulnerability alerts are not enabled on the repository. Enable them: gh api repos/{repo}/vulnerability-alerts --method PUT'), ('workflows', 'Workflows: Read and Write', 'repos/{repo}/actions/workflows?per_page=1', 'Workflows: token has access', ''))

The PAT permission probes, one per PatPermissionResults field.

repomatic.github.token.probe_pat_permission(repo, probe)[source]

Run one PAT permission probe against repo.

Parameters:
  • repo (str) – Repository in ‘owner/repo’ format.

  • probe (PatProbe) – The PatProbe row to execute.

Return type:

tuple[bool, str]

Returns:

Tuple of (passed, message).

class repomatic.github.token.PatPermissionResults(administration, contents, issues, pull_requests, vulnerability_alerts, workflows)[source]

Bases: object

Results of all PAT permission checks.

Each field holds a (passed, message) tuple from the corresponding PAT_PERMISSION_PROBES row.

administration: tuple[bool, str]

Result of the administration PAT_PERMISSION_PROBES row.

contents: tuple[bool, str]

Result of the contents PAT_PERMISSION_PROBES row.

issues: tuple[bool, str]

Result of the issues PAT_PERMISSION_PROBES row.

pull_requests: tuple[bool, str]

Result of the pull_requests PAT_PERMISSION_PROBES row.

vulnerability_alerts: tuple[bool, str]

Result of the vulnerability_alerts PAT_PERMISSION_PROBES row.

workflows: tuple[bool, str]

Result of the workflows PAT_PERMISSION_PROBES row.

iter_results()[source]

Every probe’s (passed, message) pair, in field order.

Each field is a plain tuple[bool, str] filled by check_all_pat_permissions(), so there is nothing optional to filter: the one loop serves both the full listing and failed()’s view of it.

Return type:

list[tuple[bool, str]]

failed()[source]

Return (field_name, message) pairs for each failed check.

Return type:

list[tuple[str, str]]

repomatic.github.token.check_all_pat_permissions(repo)[source]

Run all PAT permission checks and return structured results.

This is the single entry point for PAT permission validation. Both lint-repo and setup-guide call this function so that adding a new permission check benefits all consumers automatically.

Parameters:

repo (str) – Repository in ‘owner/repo’ format.

Return type:

PatPermissionResults

Returns:

PatPermissionResults with all check outcomes.

repomatic.github.token.validate_gh_token_env()[source]

Check that a GitHub token environment variable is set.

Lookup order: REPOMATIC_PAT > GH_TOKEN > GITHUB_TOKEN, matching run_gh_command.

Raises:

RuntimeError – If no variable is set.

Return type:

None

repomatic.github.token.validate_gh_api_access()[source]

Smoke-test the GitHub API and return parsed response.

Calls GET https://api.github.com/rate_limit with the token from environment variables.

Does not go through get_json(), which returns only the parsed body: this is the one caller that needs the response headers, since X-OAuth-Scopes is what tells a classic PAT apart from a fine-grained one. It still borrows that module’s DEFAULT_TIMEOUT, so a stalled connection fails the check instead of hanging the job that runs it.

Return type:

tuple[int, dict[str, str], str]

Returns:

Tuple of (status_code, headers, body).

Raises:

RuntimeError – If the API returns a 4xx/5xx status, or cannot be reached at all (network error, timeout).

repomatic.github.token.validate_classic_pat_scope(required_scope)[source]

Validate that the GitHub token is a classic PAT with the required scope.

Checks:

  1. A GitHub token environment variable is set.

  2. GitHub API is reachable (smoke-test GET).

  3. Token is a classic PAT (has X-OAuth-Scopes header).

  4. Token has the required scope.

Parameters:

required_scope (str) – The OAuth scope to require (e.g. "notifications").

Return type:

list[str]

Returns:

The full list of scopes on the token.

Raises:

RuntimeError – If any check fails.