atmos doctor
atmos doctor checks authored project configuration, deprecations, and upstream
repository health. atmos doctor --fix applies supported migrations only after
verifying their runtime field behavior and unrelated YAML values.
Choose schemas and file patterns for project validation.
Usage
atmos doctor
atmos doctor --fix --dry-run
atmos doctor --fix
atmos doctor --format=json
atmos doctor --fail-on=warning
atmos doctor --only=deprecations
atmos doctor --only=sources
atmos doctor --fix --only=component-monorepo
atmos doctor --affected --base=origin/main
atmos doctor --exclude='tests/fixtures/**'
atmos doctor --list-checks
atmos doctor --offline
Flags
--fix- Apply eligible, verified repairs. Disabled by default.
--dry-run- Stage and verify repairs without writing files or backups. Requires
--fix. --format- Choose
text(default),rich,json, orsarif. Both human formats show compiler-style diagnostics with source excerpts. --fail-on- Fail on
error(default),warning, ornone. Operational failures always fail. Notice-level findings never count toward the threshold. --only- Comma-separated check IDs or categories. All built-in checks run by default.
--exclude- Repeatable project-relative glob pattern. Supports
**. --affected- Scope checks to changed inputs, expanding configuration and stack changes to their dependent checks.
--base- Git comparison base for
--affected. Uses the existing validation base selection when omitted. Requires--affected. --list-checks- Print the built-in check catalog to stdout without inspecting the project. Combine it only with
--formatand--fail-on; any other doctor flag is rejected, as is--format=rich. --offline- Disable repository metadata requests and the GitHub lookups that verify component moves. Disabled by default.
Doctor also honors the global --chdir, --config, --config-path, and --profile flags,
which can appear before or after the command name.
Invalid option values and combinations exit 2 before any project file is read. The error
names the option and lists the valid values. With --format=json or --format=sarif, the
same failure is written to stdout as a report whose operational_error field holds the
message, so tools parse one shape for every outcome.
Configuration discovery
Doctor finds the Atmos configuration the same way every other command does, without parsing it first, so it can diagnose a configuration that is malformed.
- Running from a subdirectory finds the nearest parent directory that contains
atmos.yaml, and that directory becomes the project root. ATMOS_CLI_CONFIG_PATHselects the configuration directory and turns off parent and Git-root discovery.--configselects one or more configuration files, and--config-pathselects one or more directories. Either one turns off automatic discovery, as it does for Atmos itself.atmos.dand.atmos.ddirectories are read relative to the configuration file's directory, not the current directory.- Only active profiles are inspected: those named by
--profileorATMOS_PROFILE, andprofiles.defaultwhen neither is set. A profile that cannot be found is reported. - Home and system configuration is not part of the project. Its presence is reported as an input doctor cannot verify.
When no project configuration can be found, doctor stops with an operational failure and
exits 2, because a clean result for a project it never found would be misleading.
Checks and repairs
schema- Configuration, authored stack, and configured custom schemas.
yaml-functions- Unknown YAML function names, such as
!terraform.staet, with source locations and suggestions for unambiguous spelling corrections. Uses Atmos's canonical function catalog without executing functions. Also checks quoted function strings in known dotted namespaces such as!terraform.*. Native explicit YAML tags, including!!strand verbatim type tags, retain their YAML meaning; explicitly typed strings, URI tags, literal values, and unrelated strings are not interpreted as function names. Argument signatures and runtime results are not validated. imports- Local source discovery and unresolved-input diagnostics. Imports resolve the way Atmos resolves them: relative to the importing file, with the
.yaml,.yml, and template extensions tried in turn. stacks- Production inheritance and duplicate-instance checks for complete static inputs.
editorconfig- Existing EditorConfig validation.
github-actions- Existing actionlint validation. A
.github/actionlint.yamlfile is honored, so custom self-hosted runner labels declared there are not reported. deprecated-field- Schema-annotated deprecations, with replacement guidance.
command-provides- Argument
typeand flagsemantic_typetoprovides, forstackorcomponent. hook-event- Recognized hyphenated lifecycle events to dotted events.
terraform-reconfigure- Explicit
init_run_reconfigure: falsetoinit.reconfigure: never, ortruetoauto. modernization- Advisory findings for legacy
cloudposse/github-action-atmos-*GitHub Actions. component-monorepo- Component sources that still point at the archived
cloudposse/terraform-aws-componentsmonorepo. Doctor moves each one to itscloudposse-terraform-componentsrepository at the same version, after verifying the files are identical. It belongs to themodernizationcategory. source-repository- Vendor, component
source, andcomponent.yamlspec.source.uriupstream repository health. A check with nothing to inspect is reported as skipped, not passed.
Select check IDs or categories (validation, deprecations, modernization,
sources) with --only. Ordinary custom-command flag type is not deprecated.
Doctor reads YAML without running hooks, project commands, authentication flows,
provisioning, or automatic tool installation. It does not evaluate templates,
remote imports, remote state, secrets, or executable YAML functions to justify a
repair. Imported template files such as *.yaml.tmpl are reported as not evaluated
and are never parsed as plain YAML. Remote custom schemas and schemas with external
references are reported as incomplete; their complete input closure cannot yet be
captured. Excluded sources, layered configurations, and profiles can leave
verification incomplete. The report states this explicitly. Full runtime
validation remains available through atmos validate stacks.
A failure in one check, such as an unreadable file, is recorded against that check. The
remaining checks still run, and the run exits 2 at the end.
A repair is blocked only by inputs that can change what it means. Templates and YAML
functions elsewhere in a file, such as custom command steps, description, or
stacks.name_template, do not block a migration of components.terraform or of a
command's arguments and flags. A repair is blocked when a dynamic value sits in the
keys the repair reads, or when another configuration layer could change the edited
value: an import, an atmos.d file, an active profile (including profiles.default),
or configuration outside the project. Which of these applies depends on the repair: stack
and workflow files are not layered configuration, so a template in one of them does not
block a change to atmos.yaml. Every blocked finding names the file and line, or the layer,
that blocked it and tells you how to make the change by hand. A finding that no repair can
resolve never suggests running --fix.
Doctor automatically moves component-level Terraform settings.depends_on lists to
dependencies.components when each entry contains a literal component and an
optional literal stack. It preserves comments and unrelated settings. This repair
intentionally adopts modern required-dependency semantics: missing or disabled targets
and dependency cycles are reported as errors, even when the migration itself succeeds.
--fix --dry-run reports errors from the proposed configuration without changing files.
These errors remain visible after repairs even with --only=deprecations.
Dependency repairs require complete static inputs so doctor can validate the resulting
production dependency graph. Legacy context selectors, file/folder entries, other
legacy shapes, and declarations that already contain dependencies.components require
manual review. Stack naming migrations also remain manual.
Source repository health
Doctor checks literal vendor sources and component source/source.uri declarations.
GitHub and configured GitHub Enterprise metadata checks use existing credentials,
timeouts, and one request per repository per check phase. Other hosts and dynamic
URLs are reported as incomplete coverage. Use --offline to disable network checks.
With --offline, each repository the check would have queried produces a notice. Notices
are informational: they do not change the exit code, even with --fail-on=warning.
Archived repositories produce warnings. Confirmed removal is distinguished from
an inaccessible repository. A GitHub 404 can mean private content or missing
permissions, so doctor does not call it confirmed deletion. Rate limits, access
denials, and network failures are inconclusive. The source health check never rewrites
source URLs; only the component-monorepo repair does, after verifying the files are identical.
Moving components off the archived monorepo
Cloud Posse archived the cloudposse/terraform-aws-components monorepo and moved its
components to individual repositories in the cloudposse-terraform-components organization.
A few components were split into several repositories or removed before the move, so only
components with a successor repository can be migrated automatically; the rest are
reported for manual review. A source that still names the monorepo keeps working today, but
it receives no further fixes. The component-monorepo check finds these sources and moves
them without changing the version you pinned:
# vendor.yaml, before
- component: dns-primary
source: "git::https://github.com/cloudposse/terraform-aws-components.git//modules/dns-primary?depth=1&ref=1.495.0"
# vendor.yaml, after
- component: dns-primary
source: "git::https://github.com/cloudposse-terraform-components/aws-dns-primary.git//src?depth=1&ref=1.495.0"
The successor repositories keep the monorepo's historical tags, such as 1.495.0, and the
component code lives in their src directory. Doctor changes only the repository and the
path: the scheme, the .git suffix, the ref, other query parameters such as depth=1,
the version field, comments, and quoting stay as written. A ref={{ .Version }} placeholder
is kept as it is and keeps using the version field beside it.
Several successor names differ from the module path. For example, aws-sso moved to
aws-identity-center and argocd-repo moved to argocd-github-repo, so doctor looks every
component up in the map Cloud Posse published instead of deriving the name.
Doctor reads these locations, including every vendor manifest that atmos vendor pull reads
through spec.imports:
spec.source.uriin acomponent.yamlspec.sources[].sourcein a vendor manifest, such asvendor.yamland the manifests it importssourceandsource.uriof a component in a stack
Verification
Before any repair, doctor resolves the version the source fetches: the literal ref, or the
version field for a {{ .Version }} placeholder. It then reads the Git tree of
modules/<component> in the monorepo and the tree of src in the successor repository at that
tag through the GitHub API, with the credentials and timeouts the other GitHub checks use. Equal
trees mean the files are byte-identical, and only then is the repair available. One lookup
serves every source that uses the same component and version.
| Result | Finding status |
|---|---|
| The trees are equal | available, or fixed with --fix |
| The trees differ, or the tag is missing from either repository | verification-failed, with the reason |
Verification was skipped with --offline, GitHub rate limited the request, or the request failed | verification-incomplete, with the reason |
Nothing is written for a source that is not available. The check never changes a version, so
run it before atmos vendor update if you also want newer releases.
Sources that need a manual change
Doctor reports these sources with a specific explanation and leaves them untouched:
- Components that were split into subcomponents, such as
tgwandspacelift, have no single successor repository. Follow the special migration procedure. - Components that were removed before the migration, such as
eks/efs, have no successor. - A version that is not one tag, such as
CUSTOM, > 1.422or a semver range, cannot be checked. Pin one tag first. - A source without a
ref, with a templated repository or path, or written as an SSH URL. - Mixins (
spec.mixins[].uri) that point at the monorepo.
Verification and recovery
Repairs are staged in memory at their original logical paths. Doctor compares actual runtime field consumers, all unrelated YAML nodes, and candidate schema results using fresh validators. Conflicting destination fields, lost comments, unsupported anchors, and failed comparisons prevent the repair. There is no flag to bypass verification. A dry run writes neither project files nor backups.
Before writing, doctor warns about affected dirty, staged, untracked, or ignored
files, or unavailable Git. --fix authorizes eligible edits without another prompt.
Doctor never changes Git state.
A repair rewrites the file through the YAML editor, which preserves comments, tags, and anchors but can reformat other parts of the file. Blank lines and folded scalars may change their layout. The values are unchanged and verified; only the formatting differs. Review the diff of a repaired file before committing it.
Comments on a migrated key move with it: the legacy key's head, inline, and foot comments are carried onto the replacement key, every other comment must be unchanged, and comment blocks at the end of a document are compared as text.
Exact originals are saved beneath the Atmos XDG data directory at
doctor/backups/<run-id>, with private permissions. manifest.json records each
original path, permission mode as an octal string such as "0644", backup filename,
and before/after SHA-256 hashes.
To recover, compare the current file to after_sha256, copy its .original backup
to the recorded path, and restore the recorded permission mode. Review later edits
before overwriting them.
All backups finish before the first write. Each replacement is atomic, but a multi-file run is not a transaction. Failures or cancellation stop further writes; the report identifies files already changed and retains their backups. Doctor reads symlinked sources but never writes through them. Findings in a symlinked file are reported for manual review, and repairs in other files still proceed.
Output and exit codes
The default output shows each finding with its severity, rule, file location, and a
YAML source excerpt with a caret at the reported location. Repair status, suggested
actions, and explanations appear on separate lines. --format=rich uses the same
presentation. Themes and --no-color apply to both formats.
Live progress is replaced by concise phase summaries. The final report includes
skipped or incomplete checks, recovery instructions when applicable, and a single
atmos doctor --fix suggestion when repairs are available. A dry run reports verified
repairs separately from applied changes. Missing source files do not hide findings.
Human reports and progress use stderr. Non-terminal output uses the same report
layout without animation. JSON and SARIF use stdout and suppress human output. JSON includes fix decisions, changed files,
verification coverage, backup location, dry_run, and operational_error for operational
and invocation failures.
Without --fix, the summary counts what remains and what a repair run could apply, for
example 3 findings (2 fixable)., followed by Run: atmos doctor --fix. With --fix, it
reports what it applied, for example Fixed 2 findings in 1 file. 1 finding remains. A dry
run reports the repairs it verified, for example 2 repairs verified. 3 findings remain.
Warnings are advisory by default. --fail-on=warning makes them blocking;
--fail-on=none suppresses finding-based failure. Exit codes are 0 when the
threshold is satisfied, 1 for blocking findings, 2 for operational or invocation
failures (including a missing project configuration), and 130 for cancellation. Fixed findings no longer count toward the
threshold; dry runs evaluate the unchanged files.
--fix also exits 1 when a repair it attempted fails verification, whatever the
--fail-on threshold, because the run did not deliver what you asked for. A failed
write is an operational failure and exits 2. A dry run reports a rejected repair
but only applies the threshold.