repomatic.lint_repo.settings module¶
Checks of the repository settings the setup guide asks for.
The Actions policies and the immutable releases setting each read one endpoint,
through a RepoSettingProbe. The rulesets and the branch protection
rules decide how the default branch is protected. The PyPI Trusted Publisher
entry, the repository secrets and the REPOMATIC_PAT permissions complete the
list.
Note
Most of these endpoints need the Administration: Read-only permission. A
token without it gets a 403, which a check reports as skipped, never as a
failure.
- class repomatic.lint_repo.settings.RepoSettingProbe(label, endpoint, field, pass_message, fail_message, passing=None, failing=frozenset({}), unknown='unknown value {value!r}', unavailable='could not query API')[source]¶
Bases:
objectOne repository setting to read, and how its values map to verdicts.
The repository-setting checks share one shape: read one API endpoint through
gh, inspect one field, and map its value onto a pass, a failure naming the settings page, or an indeterminate skip. What varies is declared here, andcheck()runs it. A probe with no passing set reads its field as a boolean toggle and never skips on an unknown value.- failing: frozenset[str] = frozenset({})¶
Values that fail explicitly; anything in neither set skips.
- unknown: str = 'unknown value {value!r}'¶
Skip reason for a value in neither set;
{value}interpolates it.
Skip reason when the endpoint cannot be read.
- repomatic.lint_repo.settings.FORK_PR_APPROVAL_POLICY: Final = RepoSettingProbe(label='Fork PR approval policy check', endpoint='actions/permissions/fork-pr-contributor-approval', field='approval_policy', pass_message='Fork PR approval policy: {value}.', fail_message="Fork PR approval policy is 'first_time_contributors_new_to_github', which only catches brand-new GitHub accounts. Set it to 'first_time_contributors' (or stricter) under https://github.com/{repo}/settings/actions to require approval for any first-time contributor.", passing=frozenset({'all_external_contributors', 'first_time_contributors'}), failing=frozenset({'first_time_contributors_new_to_github'}), unknown='unknown policy {value!r}', unavailable='could not query API')¶
The fork PR approval policy, read by
check_fork_pr_approval_policy().
- repomatic.lint_repo.settings.SHA_PINNING_REQUIRED: Final = RepoSettingProbe(label='SHA pinning required check', endpoint='actions/permissions', field='sha_pinning_required', pass_message='SHA pinning required: enabled.', fail_message='SHA pinning is not required for GitHub Actions in this repository. Enable it under https://github.com/{repo}/settings/actions (Actions permissions → Require actions to be pinned to a full-length commit SHA) so GitHub rejects any unpinned action reference, not just the ones zizmor happens to catch.', passing=None, failing=frozenset(), unknown='unknown value {value!r}', unavailable='could not query API')¶
The
sha_pinning_requiredpolicy, read bycheck_sha_pinning_required().
- repomatic.lint_repo.settings.IMMUTABLE_RELEASES: Final = RepoSettingProbe(label='Immutable releases check', endpoint='immutable-releases', field='enabled', pass_message='Immutable releases are enabled.', fail_message='Immutable releases are not enabled.', passing=None, failing=frozenset(), unknown='unknown value {value!r}', unavailable='could not query API')¶
The immutable releases setting, read by
check_immutable_releases().
- repomatic.lint_repo.settings.BRANCH_PROTECTION_RULES_QUERY = '\nquery($owner: String!, $name: String!) {\n repository(owner: $owner, name: $name) {\n branchProtectionRules(first: 100) {\n nodes { pattern }\n }\n }\n}\n'¶
Lists every branch protection rule a repository declares, by branch pattern.
GraphQL because REST cannot answer the question. GET /repos/{repo}/branches/{branch}/protection reads one concrete branch, so finding a rule needs the branch name up front and still misses any pattern targeting a branch that does not exist yet. This field enumerates the rules themselves.
The field also draws the line the check depends on: a ruleset never appears here, and a branch protection rule never appears under
rulesets. Verified on 2026-08-27 against a repository holding both at once, which listed the branch protection rule here and the ruleset only there.
- repomatic.lint_repo.settings.check_fork_pr_approval_policy(ctx)[source]¶
Check that fork PR workflows require approval for first-time contributors.
GitHub Actions has a per-repository policy that controls when workflows from fork pull requests must be approved by a maintainer before they run. The three values, from weakest to strongest, are
first_time_contributors_new_to_github,first_time_contributors, andall_external_contributors.The default (
first_time_contributors_new_to_github) only catches brand-new GitHub accounts, which is trivial to bypass with a slightly aged account. The minimum acceptable setting isfirst_time_contributors, which requires approval for any first-time contributor to this repository. This is one of the mitigations recommended in Astral’s open-source security post: see https://astral.sh/blog/open-source-security-at-astral.Note
The endpoint needs the
Administration: Read-onlypermission. When theREPOMATIC_PATlacks it, or the API call fails for any other reason, the check reports an indeterminate result rather than a negative one.- Return type:
- repomatic.lint_repo.settings.check_sha_pinning_required(ctx)[source]¶
Check that GitHub Actions must be pinned to a full-length commit SHA.
GitHub has a per-repository policy,
sha_pinning_required, that makes the platform itself refuse to run any workflow referencing an action by a mutable tag or branch instead of a commit SHA. repomatic already pins every action it generates and checks unpinned refs withzizmor(check_inline_pins_match_upstream()and thelint-zizmorjob), but azizmorfinding can be silenced inline (# zizmor: ignore[...]), so a hand-edited workflow could still slip a mutable tag past review. This repository setting is the platform-enforced backstop.Note
The endpoint needs the
Administration: Read-onlypermission. When theREPOMATIC_PATlacks it, or the API call fails for any other reason, the check reports an indeterminate result rather than a negative one.- Return type:
- repomatic.lint_repo.settings.check_tag_protection_rules(ctx)[source]¶
Check that no tag ruleset can block the
create-tagworkflow job.A tag ruleset that restricts creation or requires status checks can stop
REPOMATIC_PAT(orGITHUB_TOKEN) from pushing release tags. The check warns about any active ruleset that targets tags.- Return type:
- repomatic.lint_repo.settings.check_branch_ruleset_on_default(ctx)[source]¶
Check that at least one active branch ruleset exists.
Reads the same rulesets as
check_tag_protection_rules(), and looks for an active ruleset withtarget == "branch". The presence of any such ruleset is taken as evidence that the default branch is protected (restrict deletions and block force pushes).Note
This is a heuristic: it does not verify the ruleset targets the default branch specifically, nor that it enables the exact rules the setup guide recommends. A deeper check would read each ruleset’s conditions through
GET /repos/{repo}/rulesets/{id}, adding N+1 API calls.- Return type:
- Returns:
A
CheckResult.passedisNonewhen the rulesets API could not be read, matchingcheck_tag_protection_rules(), which reads the same payload.
- repomatic.lint_repo.settings.check_classic_branch_protection(ctx)[source]¶
Check that no branch protection rule survives beside the rulesets.
Branch protection rules predate rulesets and GitHub still supports both, with no deprecation notice and no removal date. They are not alternatives a repository picks between: both apply at once, and where the two carry the same rule, the stricter version wins. A rule left behind after a migration therefore changes nothing on the day it is left, which is what makes it worth reporting: the branch policy is now split across two settings pages, and an edit to one page does not show on the other.
check_branch_ruleset_on_default()states the other half of the same policy, that a ruleset must exist. Together they say the protection is a ruleset and only a ruleset.Note
Advisory, not fatal. A leftover rule protects the branch rather than exposing it, so the finding is a cleanup.
- Return type:
- Returns:
A
CheckResult.passedisNonewhen the API could not be read, matching the ruleset checks. Reading the rules needs admin on the repository, so a token without it skips rather than passes.
- repomatic.lint_repo.settings.check_immutable_releases(ctx)[source]¶
Check that immutable releases are enabled for the repository.
Note
The endpoint needs the “Administration: Read-only” permission on fine-grained PATs. When the
REPOMATIC_PATlacks it, the check reports an indeterminate result rather than a negative one.- Return type:
- repomatic.lint_repo.settings.check_pypi_trusted_publisher(ctx)[source]¶
Check that the PyPI Trusted Publisher entry is registered for this repo.
PyPI’s Trusted Publisher settings are owner-only at
/manage/project/<name>/settings/publishing/and not exposed through any public API. The only public surface where the OIDC publisher is observable is the PEP 740 provenance attached to releases uploaded via OIDC: seerepomatic.pypi.get_trusted_publishers(). This check probes the latest release’s provenance and looks for a bundle whoserepositorymatches the repository and whoseworkflowisPYPI_TRUSTED_PUBLISHER_WORKFLOW. A match means the publisher is wired up and a previous release uploaded successfully through it. A mismatch (provenance exists but names a different repo or workflow) is a misconfiguration: typical cause is registering the upstream reusable workflow instead of the downstream caller’srelease.yaml, which fails on the first upload after migration. Indeterminate (None) covers two cases that look identical from the outside: no published release yet, and provenance missing because past releases were uploaded via API token. In both cases the setup guide nags until the next OIDC-attested upload appears.- Return type:
- repomatic.lint_repo.settings.check_virustotal_secret(ctx)[source]¶
Report whether the VirusTotal API key is available to release builds.
- Return type:
- repomatic.lint_repo.settings.check_notifications_secret(ctx)[source]¶
Report whether the unsubscribe workflow has the token it needs.
- Return type:
- repomatic.lint_repo.settings.check_pat_permissions(ctx)[source]¶
Yield one result per PAT permission probe, or a skip note without a PAT.
The probes are the rows of
PAT_PERMISSION_PROBES. They need a repository to read, so a PAT with no repository yields nothing.- Return type:
- repomatic.lint_repo.settings.check_pat_scope(ctx)[source]¶
Check that the PAT reaches no other repository of the same owner.
Reads the probe of
repomatic.github.token.check_pat_repository_scope(), which the setup guide reads too.- Return type: