Troubleshooting

Every error atlantide raises has a name, and the name says which stage failed. Run any command with --debug to get the full traceback and cause chain instead of the one-line rendering.

The stage numbers below refer to the pipeline.

Config does not compile (stages 1–2)

LanguageError — the config uses a construct outside the Atlas-lang subset. The message names the construct, the line, and the replacement:

error: construct 'While' is not allowed in Atlas-lang — Atlas-lang has no
`while` (halting must be provable); use a bounded `for`. (line 2, col 0)

See what config may not do for the full list. import os fails the same way: imports are restricted to atlantide.*.

FuelExhaustedError — evaluation exceeded its step budget. Almost always a runaway recursion or a loop over something far larger than intended. Recursion is not rejected at parse time, so this is where it surfaces.

IRError — a value reached a resource field that cannot be canonicalized. Config data must be plain JSON-shaped values.

RegistryError — an unknown or duplicate name, or a bad version. Two resources sharing a logical name in one stack, a provider that is not installed, or a component alias that was never added.

The graph is not a DAG (stage 4)

CycleError — resources depend on each other in a loop. The message names the cycle. Usually two resources each reading a field from the other; break it by passing a literal on one side, or by splitting the resource.

StackOutputCycleError — cross-stack output() references form a loop, for example common:vpc_id -> dev:x -> common:vpc_id. Raised before lowering, so the chain names the output keys rather than the resources.

The plan is refused (stage 7)

PreventDestroyError — a planned destroy hit a resource with Lifecycle(prevent_destroy=True). Deliberate: --replace and destroy honour it too. Remove the guard in config and re-plan if the destroy is intended.

PolicyViolationError — one or more mandatory policies failed, and the apply is blocked. plan exits 1. Fix the resource, or lower the binding to level=PolicyLevel.ADVISORY if it should warn rather than block. See Policies.

PolicyConfigError — a policy binding passes arguments the policy cannot use, e.g. enforce("require-tags", keys=3). The message names both.

Locking and concurrency (stage 8)

LockError — the lease could not be acquired: another run holds it. The run never started, so nothing was written. Wait, or inspect with atlantide state unlock.

LeaseLostError — the run did start, wrote to the provider, and then found its lease taken by someone else. Nothing is rolled back: a compensation is itself a state write, and a run that no longer holds the lock must not make one. Run atlantide refresh to see what actually exists before applying again.

FencedWriteError — the store refused a write because the recorded holder is someone else. Distinct from LeaseLostError in who decided: a run whose local clock still believes its lease is good can be wrong, a conditional write against the recorded holder cannot. This is the last line between two concurrent runs and a silently merged state.

PlanDriftError — the changeset about to run is not the one that was approved. Apply re-diffs once it holds the lease — it must, or a resource another run created in the meantime would be planned as a CREATE and built twice — so the plan a human read and the plan about to execute can differ. The message names what was added and what disappeared:

state changed between the plan you approved and the lock being taken, so the
changes are no longer the ones shown — now also: delete aws.S3Bucket:old.
Re-run to plan against current state.

Re-run. It is raised rather than reconciled because the gap between the two plans is exactly where an unreviewed destroy fits.

During apply

ProviderError — a provider CRUD call failed. Carries structured context: which resource (node_id), which phase (op), which type (resource_type). Under the default --on-failure rollback the nodes already completed are undone; see When apply fails.

RollbackError — a compensation could not complete after a failed apply. This is the case that needs attention: a compensation is a provider call followed by a state write, so a partial one can leave state describing a resource that is no longer there — and because the stored hash still matches config, the next plan reports NOOP. Run atlantide refresh before trusting the next plan. Raised alongside the original failure, never instead of it.

InterruptedRunError — Ctrl-C. Not an infrastructure failure, so it exits 130 rather than 1 and drops the error: framing. Completed nodes are compensated on the way out, where the run still held its lock.

State, secrets, artifacts, components

StateError — the state backend operation failed. With a remote backend, atlantide state check verifies reachability and setup up front rather than surfacing one failed API call at a time.

SecretsError — sealing or unsealing failed: unknown backend, wrong key, or corrupt ciphertext. If plan shows every secret as rotated and none intact, the keyfile is the wrong one — plan recognises that pattern and warns rather than rendering a page of spurious updates. See Secrets.

ArtifactError — a .atlas artifact is malformed, corrupted, or fails its hash check. atlantide verify performs that check on its own.

ComponentError — fetching, vendoring or verifying a component failed: a bad git source, a missing --subdir, or a vendored tree whose content hash no longer matches atlantide.lock. atlantide component vendor rebuilds .atlantis/ from the lock; component verify re-hashes it.

Things that are not errors

A plugin that failed to load. atlantide providers lists it and exits 1. Config simply cannot find that provider's types, which otherwise presents as a typo. --no-plugins ignores installed plugins entirely — useful for reproducing a build or bisecting a plugin that has broken a run.

A plan that shows changes you did not make. The diff compares desired inputs against recorded inputs, not against the live world. Something changed the config or the state, not the cloud. To compare against reality, use atlantide refresh.

A resource the provider could not find during refresh. It is reported but kept, unless --prune is given. One failed read is not enough evidence to discard the only record that a resource exists — discarding it means the next apply builds a second one.