rrt doctor
rrt doctor
Section titled “rrt doctor”Validate the core automation health of the resolved rrt configuration.
Overview
Section titled “Overview”rrt doctor is the basics-first repository health check. It focuses on the
shared automation wiring around the resolved configuration — local hooks, CI
workflows, and guidance to the feature-specific checks that own deeper policy
validation.
What it checks
Section titled “What it checks”The command checks the automation surfaces that tell you whether repository basics are wired correctly:
.pre-commit-config.yamlwhen presentlefthook.ymlwhen present.husky/*hook scripts when present.github/workflows/*.yml/.yamlwhen present- CI config for cross-pipeline artifact fetches, cross-checked against
[tool.rrt.artifact_protection]— see Artifact protection below
The checks are intentionally light-touch: they verify presence, readability, and whether the file appears to reference repo-release-tools policy checks. They do not replace the deeper feature validators.
Artifact protection
Section titled “Artifact protection”Some CI jobs fetch a build artifact produced by a different pipeline run. A docs job might pull a report built by an earlier release pipeline. That dependency lives only in the consuming job’s config, never on the artifact itself. Nothing on the forge side can infer that it exists, so a storage cleanup or retention policy has no way to know the artifact is still needed. When the artifact expires, the consuming job breaks.
Degrading gracefully is not the safer outcome. A consumer that skips the missing artifact and carries on hides the broken dependency instead of surfacing it. Graceful degradation and silent breakage are the same failure underneath, only harder to notice.
rrt doctor closes this gap. It scans CI config for cross-pipeline
artifact fetches, meaning steps that download an artifact built by a
different pipeline run. Each fetch is cross-checked against an optional
[tool.rrt.artifact_protection] declaration, where consumed_by records
the CI file that performs the fetch.
This is unrelated to rrt artifacts. That feature tracks content-addressed
integrity of generated files kept in the repo, and only shares a name
fragment.
The scanner recognizes two forms:
- GitLab
.../jobs/artifacts/<ref>/raw/<path>?job=<name>URLs. These match in every CI file the scanner reads:.gitlab-ci.yml, any.gitlab/**/*.ymlor*.yamlfile, and also.github/workflows/*.ymlor*.yaml. The.gitlab/tree is scanned recursively, because GitLab’sinclude: local:accepts any filename in any subdirectory. Covering GitHub files is deliberate. A job running on GitHub Actions can stillcurlan artifact out of a GitLab instance, and scoping the check to.gitlab*files would make that fetch invisible. One physical line can carry several fetches, as in chainedcurl ... && curl ...commands. Every fetch on the line is found, not just the first. - GitHub Actions
actions/download-artifact@…steps carryingrun-id:in.github/workflows/*.ymlor*.yaml. Same-pipeline downloads omitrun-idand are ignored, because they are not cross-pipeline. A step withrun-id:but noname:is still recorded. That is standard GitHub usage meaning “download every artifact from this run”, and it is common in publish and release jobs. See “Download-all fetches” below. GitLab’s same-pipeline equivalent is out of scope for the same reason. A job declaringneeds: [other_job]withartifacts: truepulls output from the same pipeline run. Those artifacts are never at risk from a retention cleanup of other pipelines, so the scanner does not look forneeds:at all.
Declare what each fetch is allowed to depend on:
[tool.rrt.artifact_protection]protected_refs = ["main"] # reserved: parsed but not yet enforced, see below
[[tool.rrt.artifact_protection.consumed]]job = "build:report_html"ref = "main"artifacts = ["public/report.html"]consumed_by = [".gitlab/65-docs.yml"]reason = "Docs pipeline embeds the report built by this job."protected_refs above is shown for illustration only. It is parsed and
validated, but no check reads it yet. Do not rely on it to protect main.
See “protected_refs is reserved, not yet enforced” below.
Each consumed entry uses five fields. job and ref identify the fetch,
as described under “Matching” below. artifacts lists the paths the fetch
may pull. consumed_by names the CI files that perform the fetch. reason
records why the dependency exists. The last two are documentation only, and
nothing in the check validates them. Matching uses job, ref, and path
when known.
The check fails when a scanned fetch has no matching consumed entry. The
failure message names the CI file and line. It also prints a ready-to-paste
[[tool.rrt.artifact_protection.consumed]] block.
The check warns without failing when a consumed entry matches no scanned
fetch. A stale declaration protects nothing real, and trains people to
ignore the check. Both directions can be reported together in one run.
When no cross-pipeline fetches are found and no
[tool.rrt.artifact_protection] block is configured, the check is a soft
warning rather than a failure. There is nothing to protect yet.
Matching: (job, ref), plus path when known
Section titled “Matching: (job, ref), plus path when known”A fetch is declared only when a consumed entry matches on both job
and ref. Matching on job alone is not enough. One correctly-filled entry
would then mask an unrelated, undeclared fetch that merely shares its job
string. Generic names such as build, report, dist and coverage are
common. GitLab’s job holds a job name while GitHub’s holds an artifact
name, so two namespaces share a single match field.
Neither job nor ref is ever split, lowercased, or otherwise normalized.
Job names may legitimately contain colons. For example,
build:report_html:bundle is a distinct job from build:report_html.
When the fetch’s path is known, the matched entry’s artifacts list must
also contain that exact path. Otherwise the fetch still counts as
undeclared. GitLab always sets path. Declaring that job X at ref Y
protects artifacts [a, b] must not silently cover a fetch of a different
artifact c from that same job and ref.
GitHub Actions download-artifact exposes no per-file path, so path is
always None. For GitHub fetches the check degrades to (job, ref) only.
That is the best available signal for that provider.
GitHub vs. GitLab: job means different things
Section titled “GitHub vs. GitLab: job means different things”job is part of the join key, but what it identifies depends on the
provider:
- GitLab — the real job name, taken from the fetch URL’s
?job=parameter. - GitHub Actions —
actions/download-artifactexposes no job identifier at all, so the scanner records the artifact’sname:field instead.
When you write a consumed entry for a GitHub Actions fetch, put the
artifact name in job, not a job or workflow name — otherwise the entry
will never match, and rrt doctor will keep reporting the fetch as
undeclared no matter what you write in job.
Download-all fetches (no name:)
Section titled “Download-all fetches (no name:)”actions/download-artifact with run-id: but no name: downloads every
artifact produced by that run — a common pattern in publish/release jobs. The
scanner cannot name a single artifact for it, so it records the fetch with
job set to the literal marker "*" (GitHub forbids ", :, <, >,
|, *, ?, CR, and LF in real artifact names, so "*" can never collide
with one). Declare it the same way as any other GitHub fetch, using the
marker verbatim:
[[tool.rrt.artifact_protection.consumed]]job = "*"ref = "12345"artifacts = ["TODO: list the artifact-relative path(s) this job consumes"]consumed_by = [".github/workflows/publish.yml"]reason = "Publish job downloads every artifact from the referenced run."Unedited TODO placeholders mean fictional protection
Section titled “Unedited TODO placeholders mean fictional protection”When rrt doctor finds an undeclared fetch, it prints a ready-to-paste
[[tool.rrt.artifact_protection.consumed]] block. For GitHub Actions
fetches, the artifact-relative path cannot be inferred from
download-artifact alone, so the suggested artifacts value is a
placeholder:
artifacts = ["TODO: list the artifact-relative path(s) this job consumes"]This block loads successfully as written. Nothing in config loading
requires artifacts entries to point at real paths. For GitLab fetches the
check verifies the declared path appears in artifacts (see the matching
rules above), so a GitLab TODO placeholder is caught immediately. For GitHub
fetches path is always unknown, so the check cannot verify it — an
unedited GitHub TODO placeholder loads cleanly and the very next rrt doctor
run will report that fetch as satisfied, even though nothing is actually
protected. Always replace both the artifacts TODO and the reason TODO
with real values before trusting a green result — an unedited placeholder
is fictional protection, not real protection.
protected_refs is reserved, not yet enforced
Section titled “protected_refs is reserved, not yet enforced”protected_refs is parsed, validated, and schema-checked, but no check in
rrt doctor currently reads it. Setting protected_refs = ["main"] records
your intent but enforces nothing today — it does not, by itself, block
anything from being deleted or flag any ref as protected. Treat it as
reserved for a future check; do not rely on it to harden anything yet.
Output and severity
Section titled “Output and severity”The command prints one grouped report for the core automation surfaces and an overall status at the end.
- unreadable automation files are errors
- missing hook-manager surfaces are obsolete when another hook manager is active
- missing optional integration surfaces are warnings when no equivalent surface is active
- surfaces that exist but do not appear to reference repo-release-tools are warnings
- readable, recognized surfaces are reported as OK
- undeclared cross-pipeline artifact fetches are errors; stale
consumedentries are warnings (see Artifact protection)
At the end, rrt doctor also points you to the feature-specific commands that
own deeper validation, such as rrt release check, rrt docs check, and
rrt eol.
Config discovery behavior
Section titled “Config discovery behavior”If no config file can be found, the command prints repository guidance and exits with an error.
If a config is auto-detected, the command emits a notice on stderr before the main report so you can tell that rrt did not use an explicitly selected file.
Examples
Section titled “Examples”rrt doctorCaveats
Section titled “Caveats”- The command reports core automation health for the resolved configuration, not just the visible file in the current directory.
- Feature-specific checks belong to their own surfaces:
rrt release check,rrt docs check, andrrt eol. - A warning does not fail the command; only error-level findings do.
Related docs
Section titled “Related docs”Badge families are intentionally complete for the current icon registry across platform, registry, and language labels; see src/repo_release_tools/tools/platform.py if you think one is missing.
Chat is powered by Context7, a third-party service with its own terms and privacy policy.