# 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.

**Configure Validation Schemas**

Choose schemas and file patterns for project validation.

Configuration Reference[Read more](/cli/configuration/schemas)

## Usage

```shell
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`
  , or 
  `sarif`
  . Both human formats show compiler-style diagnostics with source excerpts.
- **`--fail-on`**
  Fail on 
  `error`
   (default), 
  `warning`
  , or 
  `none`
  . 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 
  `--format`
   and 
  `--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`, `--profile`, and
`--logs-level` / `--logs-file` flags, which can appear before or after the command name. Root
flags such as `--cast` and `--pager` can precede `doctor` too, so a malformed `atmos.yaml` is
still diagnosed instead of failing in normal configuration loading.

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. This covers invalid options, unexpected
positional arguments, unknown flags, and a `--chdir` directory that does not exist. The failure
ends with a hint to run `atmos doctor --help`.

## 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_PATH` selects the configuration directory and turns off parent and
  Git-root discovery.
- `--config` selects one or more configuration files, and `--config-path` selects one or
  more directories. Either one turns off automatic discovery, as it does for Atmos itself.
- `atmos.d` and `.atmos.d` directories are read relative to the configuration file's
  directory, not the current directory.
- Only active profiles are inspected: those named by `--profile` or `ATMOS_PROFILE`, and
  `profiles.default` when 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. 
  `atmos.yaml`
   supports only the configuration function set (
  !env
  , 
  !exec
  , 
  !include
  , 
  !repo-root
  , 
  !git.*
  , 
  !cwd
  , 
  !random
  , 
  !unset
  , 
  !literal
  ), so a stack-only function such as 
  !store
   or 
  !terraform.state
   there is an error. In stack manifests, an untagged string is treated as a function only when Atmos would evaluate it at runtime, so 
  '!terraform.state[0]'
   is a known function. A quoted string that starts like a function in a known dotted namespace but that Atmos keeps as text, such as 
  "!git.branches are protected"
  , is a warning with a suggested spelling. Native explicit YAML tags, including 
  !!str
   and 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. Local imports, including glob patterns, are followed, so stack and dependency checks and repairs see the imported content. Templated imports, imports with 
  context
  , and remote imports are reported as not evaluated and limit verification. Doctor reports each templated import file once, at its first importer.
- **stacks**
  Production inheritance and duplicate-instance checks for complete static inputs. A pure 
  stacks.name_template
   is supported. When an input limits verification, the finding names the file and line of the blocker.
- **dependencies**
  Required-dependency validation of the effective 
  dependencies.components
   graph. Doctor reports every error with the file and line of the dependency entry: missing or disabled targets and dependency cycles.
- **editorconfig**
  EditorConfig validation. Doctor selects the same files as 
  atmos validate editorconfig
  : tracked and untracked files that Git does not ignore, filtered by the 
  validate.editorconfig
   settings in 
  atmos.yaml
  .
- **github-actions**
  Existing actionlint validation. A 
  .github/actionlint.yaml
   file is honored, so custom self-hosted runner labels declared there are not reported.
- **deprecated-field**
  Schema-annotated deprecations, with replacement guidance.
- **command-provides**
  Argument 
  type
   and flag 
  semantic_type
   to 
  provides
  , for 
  stack
   or 
  component
  .
- **hook-event**
  Recognized hyphenated lifecycle events to dotted events.
- **terraform-reconfigure**
  Explicit 
  init_run_reconfigure: false
   to 
  init.reconfigure: never
  , or 
  true
   to 
  auto
  .
- **modernization**
  Advisory findings for legacy 
  `cloudposse/github-action-atmos-*`
   GitHub Actions.
- **component-dependencies**
  Legacy component-level Terraform 
  settings.depends_on
   declarations, in map or list form, to 
  dependencies.components
  . A migration that would change any component's effective dependencies is rejected.
- **component-monorepo**
  Component sources that still point at the archived 
  cloudposse/terraform-aws-components
   monorepo. Host, owner, and repository names are matched regardless of letter case. Doctor moves each one to its 
  cloudposse-terraform-components
   repository at the same version, after verifying the files are identical. It belongs to the 
  modernization
   category.
- **source-repository**
  Vendor, component 
  source
  , and 
  component.yaml
   
  spec.source.uri
   upstream 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. Local imports are followed, so a component that imports a
catalog is verified against the merged result. Doctor compares the effective dependencies of
every concrete component before and after the migration. If the migration would change any of
them, the repair is rejected as `verification-failed` and names the difference, for example
`changes the effective dependencies of app in dev: db → vpc`. This happens when an inherited
base declares a legacy list and the inheriting component declares its own, because inheritance
replaces the list instead of merging it. 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:

```yaml
# 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"
```

```yaml
# 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.uri` in a `component.yaml`
- `spec.sources[].source` in a vendor manifest, such as `vendor.yaml` and the manifests it imports
- `source` and `source.uri` of 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.

### Source shapes

Doctor recognizes these spellings of a monorepo source:

- A mixed-case URL, such as `GitHub.com/CloudPosse/terraform-aws-components.git`. The rewrite
  keeps the spelling you wrote for everything it does not change.
- A subdirectory written inside the query string, such as
  `...terraform-aws-components.git?ref=1.495.0//modules/alb`. go-getter needs the
  `//subdirectory` before the `?`, so this source does not select that directory. Doctor
  explains this and suggests the corrected source, but leaves the file for you to change.
- A subdirectory of a component module, such as `//modules/eks/cluster/modules/foo`. The
  source moves to the parent component's repository, at `src/modules/foo`.

### 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 `tgw` and `spacelift`, have no single
  successor repository. Follow the
  [special migration procedure](https://docs.cloudposse.com/learn/maintenance/tutorials/how-to-update-components-yaml-to-new-organization/).
- 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.422` or 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 emits one notice that lists the affected dirty, staged, untracked, or
ignored files (or says that Git is unavailable). The notice does not count toward `--fail-on`.
`--fix` authorizes eligible edits without another prompt. Doctor never changes Git state.

A read-only file is never overwritten. Doctor reports it for a manual change instead, and
repairs in other files still proceed.

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. If you interrupt a run
(Ctrl-C), doctor prints the files it already changed and the backup location, and exits `130`.
With `--format=json` or `--format=sarif` the report has an `operational_error` message that
starts with `Interrupted` and summarizes the changed files; `changed_files` lists all of them. 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`. The suggested command
keeps the flags that scope the run, such as `--only`, `--exclude`, `--config`, `--profile`, and
`--offline`. With `--fix`, it reports what it applied, for example
`Fixed 2 findings in 1 file. 1 finding remains.`, or `Nothing to fix.` when no repair applied. 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.
