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 triggerson.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 thatunittest.mock.patchcan replace the module attribute after import and the decorator sees the mock at call time.
- class repomatic.github.token.PatProbe(field: str, permission: str, endpoint: str, success: str, not_found: str = '')[source]¶
Bases:
NamedTupleOne fine-grained PAT permission probe.
A read-only API call whose
HTTP 403unambiguously identifies the missing fine-grained permission. Rows live inPAT_PERMISSION_PROBES.Create new instance of PatProbe(field, permission, endpoint, success, not_found)
- field: str¶
The
PatPermissionResultsfield receiving this probe’s result.
- 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
PatPermissionResultsfield.
- repomatic.github.token.probe_pat_permission(repo, probe)[source]¶
Run one PAT permission probe against repo.
- class repomatic.github.token.PatPermissionResults(administration, contents, issues, pull_requests, vulnerability_alerts, workflows)[source]¶
Bases:
objectResults of all PAT permission checks.
Each field holds a
(passed, message)tuple from the correspondingPAT_PERMISSION_PROBESrow.- administration: tuple[bool, str]¶
Result of the
administrationPAT_PERMISSION_PROBESrow.
- contents: tuple[bool, str]¶
Result of the
contentsPAT_PERMISSION_PROBESrow.
- issues: tuple[bool, str]¶
Result of the
issuesPAT_PERMISSION_PROBESrow.
- pull_requests: tuple[bool, str]¶
Result of the
pull_requestsPAT_PERMISSION_PROBESrow.
- vulnerability_alerts: tuple[bool, str]¶
Result of the
vulnerability_alertsPAT_PERMISSION_PROBESrow.
- workflows: tuple[bool, str]¶
Result of the
workflowsPAT_PERMISSION_PROBESrow.
- iter_results()[source]¶
Every probe’s
(passed, message)pair, in field order.Each field is a plain
tuple[bool, str]filled bycheck_all_pat_permissions(), so there is nothing optional to filter: the one loop serves both the full listing andfailed()’s view of it.
- 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-repoandsetup-guidecall this function so that adding a new permission check benefits all consumers automatically.- Parameters:
repo (
str) – Repository in ‘owner/repo’ format.- Return type:
- Returns:
PatPermissionResultswith 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, matchingrun_gh_command.- Raises:
RuntimeError – If no variable is set.
- Return type:
- repomatic.github.token.validate_gh_api_access()[source]¶
Smoke-test the GitHub API and return parsed response.
Calls
GET https://api.github.com/rate_limitwith 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, sinceX-OAuth-Scopesis what tells a classic PAT apart from a fine-grained one. It still borrows that module’sDEFAULT_TIMEOUT, so a stalled connection fails the check instead of hanging the job that runs it.
- repomatic.github.token.validate_classic_pat_scope(required_scope)[source]¶
Validate that the GitHub token is a classic PAT with the required scope.
Checks:
A GitHub token environment variable is set.
GitHub API is reachable (smoke-test GET).
Token is a classic PAT (has
X-OAuth-Scopesheader).Token has the required scope.
- Parameters:
required_scope (
str) – The OAuth scope to require (e.g."notifications").- Return type:
- Returns:
The full list of scopes on the token.
- Raises:
RuntimeError – If any check fails.