Keep the command, its exit status, standard error, and saved Form together. Exit meanings depend on the command. Outputs and exit status gives each command's contract.
Check which executable your shell runs and its version:
command -v rootformrootform versionIf help still lists an unexpected command or flag, fix PATH or use the intended exact release in automation. Do not reinterpret a diagnostic from a different binary.
run needs plan JSON, state JSON, or a saved Form. A configuration directory returns status 2, names the directory in the error headline, and gives export commands (Code: DIRECTORY_INPUT). A binary saved plan returns status 3 with the same kind of guidance:
rootform run plan.tfplan --no-serveError: this input looks like a saved plan
A saved plan is read with --plan-file, next to the plan JSON exported from it.
Try: terraform show -json plan.tfplan > plan.json rootform run plan.json --plan-file plan.tfplan
Code: INPUT_UNRECOGNIZEDRun the two suggested commands: the first exports the JSON that Rootform analyzes, the second pairs it with the saved plan it came from. OpenTofu users run the export with tofu. The saved plan and JSON can contain cleartext secrets; keep them out of Git and public artifacts. Plan inputs gives the complete procedure.
Malformed JSON reports Error: this input is not valid JSON; a JSON object without plan, state, or saved Form fields reports Error: this JSON is not a plan, state or saved Form; a text file such as main.tf reports Error: this input is not JSON. Each carries Code: INPUT_UNRECOGNIZED. A state export from a working directory with no state reports the missing recorded state with the same code, followed by plan commands to run instead. The input kind is detected from content, not extension. A raw terraform.tfstate file and a terraform plan -json event stream are refused with the export commands to use instead. Re-export with terraform show -json, then confirm the file is complete before retrying. A plan with errored: true reports Error: the plan JSON records a failed plan with Code: PLAN_ERRORED; resolve the planning failure first rather than treating the result as an empty architecture.
The saved plan named by --plan-file must be the one used to make that exact JSON export. A mismatched pair records PLAN_PAIR_MISMATCH. Without --require-enrichment, analysis continues with plan JSON alone and the summary's Enrichment line says Saved plan refused (PLAN_PAIR_MISMATCH); the plan JSON was analyzed alone. With the requirement, it exits 3:
rootform run plan.json --plan-file other.tfplan \ --require-enrichment --no-serveError: saved plan does not match the plan JSON
--require-enrichment needs a verified pairing.
Re-export the JSON from the same saved plan: terraform show -json plan.tfplan > plan.json
Code: PLAN_PAIR_MISMATCHRe-export JSON from the same saved plan, then retry. An unreadable or encrypted saved plan reports PLAN_FILE_UNREADABLE; use plan-only analysis if direct values suffice, or supply a readable matching pair. Never pair an arbitrary working directory with an old plan to establish references.
The analysis can succeed while a resource remains uninterpreted. When the only instance of a plan matches no Rule, the run summary's Limited interpretation section says The only instance matched no Rule in the selected Dialects. and counts its resource type under Resource type Instances. rootform explain instance <address> --input analysis.json then prints Interpretation none: no selected Dialect has a Rule for this type. Rootform still records a Representation for that instance. Confirm the active Dialects with rootform list dialects --format wide; add or author a reviewed Dialect only if its architectural meaning is needed. If the type is outside reviewed Rule coverage, report a semantic gap.
The Explorer shows one scene at a time, so a represented instance may have no card where you are looking. Search covers the whole architecture: search by name or type, then select the result to open its containing context. A secondary resource can also appear in the Inspector of the object it contributes to. rootform explain instance <address> --input analysis.json confirms the instance in the saved Form. A missing card alone is not a missing resource; see Reveal a secondary resource.
Find the instance in the Explorer or run rootform explain instance <address> --input analysis.json. Inspect each closure's Rule, via path, result, reason, and candidate counts. A Terraform dependency alone does not establish an architectural relation. The active Dialect must emit it from values or a verified direct traversal. EMISSION_PATH_UNDEFINED means the path is absent from that instance's provider schema; VIA_VALUE_SHAPE means its shape cannot be read as endpoint identity. Correct the Dialect path or gather better input; do not add a guessed edge.
For unknown_until_apply, first check which stage the Form contains and which
stage the Policy evaluates. A paired saved plan can establish some direct
Planned-stage references even when their values are unknown. Confirm that the
saved plan matches the JSON export and that the expression uses a supported
reference form; see Traversals and scope.
The enrichment status must be verified for Rootform to use that evidence.
Transformed expressions and historical stages do not gain identity from the
pair. If no supported reference settles the endpoint, keep the closure
indeterminate until later plan or state evidence becomes available through
the normal workflow. Do not apply infrastructure solely to remove uncertainty.
Sensitive values remain masked and are never printed. A verified direct
reference may establish endpoint identity without exposing its sensitive
value. The closure model explains why
absent differs from indeterminate.
A provider.<path> emission can show indeterminate (unavailable) when the plan has no traversal from a paired saved plan, the expression is literal or transformed, or the selected stage is historical. State JSON contains no provider configuration. For a planned-stage question, supply the paired saved plan and inspect whether the provider expression directly names a resource. Rootform never reads literal provider configuration values just to force a relation.
DUPLICATE_IDENTITY means more than one eligible instance has the same matched identity. ambiguous_unknown means at least one candidate could match but its identity is unknown, sensitive, or unavailable. EVIDENCE_CONFLICT means a verified traversal and a known value point at different endpoints; the closure reason is reference_ambiguous. Inspect the candidate counts and provider configuration scope, then correct the input or Rule identity declaration. None of these is a resolved relation.
A known value with no in-scope match yields external_denied unless the emission declares external = "allow". Even with that declaration, an eligible unknown in-scope candidate prevents an external endpoint. Confirm whether the target is genuinely outside this plan's inventory before changing a Dialect. An external endpoint does not verify the remote object.
--locked requires rootform.lock directly under the selected project root. Without it, run exits 3 and reports Error: rootform.lock is required by --locked with Code: SEMANTIC_SELECTION:
rootform run plan.json --locked --no-serveError: rootform.lock is required by --locked
Code: SEMANTIC_SELECTIONFor embedded-only work, omit --locked. For an exact external selection, add content from the project root and commit the lock. If a selected local Dialect changed, the binary reports selected Dialect network-review differs from rootform.lock; use an override while editing, then rootform update dialect network-review to record a reviewed change. init cannot adopt source drift.
An invalid lock reports Error: rootform.lock is invalid and explains the expected structure. list and show carry Code: SELECTION_LOCK_INVALID and exit 3; inspect the lock as strict JSON with known fields and no duplicate keys.
A locked run exits 3 when a selected Dialect cannot be loaded; its headline names the unavailable Dialect and its code is SEMANTIC_SELECTION. rootform check exits 3 when a selected Policy Pack cannot be loaded; its headline names the unavailable Pack and its code is SELECTION_POLICY_PACK_MISSING. rootform list fails for the same reason, so read the entries in rootform.lock instead. For selected OCI content, run rootform init --locked --no-input from the project root to install the exact recorded digests; add --offline only when those bytes are already on this machine. init cannot choose another version or change the lock. A local source must be restored at its recorded path: init reports the local source is unavailable and cannot recreate it. When the project has a vendor tree, repair that tree instead, as described below.
The Rootform home holds the version named by the lock, but its bytes no longer match the recorded digest. init does not overwrite an installed version. Delete the damaged copy with the family and name@version from the diagnostic, for example rootform uninstall dialects payments@0.1.0 or rootform uninstall policy-packs baseline@0.1.0, then run rootform init --locked --no-input to install the pinned bytes again. The lock does not change. External content storage explains where installed content lives.
A locked run refuses a vendor family that no longer matches rootform.lock and exits 3. While the family directory exists, Rootform does not fall back to a local source or registry. The error headline names the problem (Code: SEMANTIC_SELECTION):
Error: selected Dialect network-review differs from rootform.lockError: vendored Dialect network-review is missing or invalidError: .rootform/dialects does not exactly match rootform.lockThe first line means that a vendored Dialect's source no longer has its locked content digest. The second means that its vendor metadata is missing or invalid. The third means that the family has a missing, extra, or unreadable entry; a .rootform/dialects path that is not a directory reports is present but does not match rootform.lock instead. Vendored Policy Packs are checked when rootform check loads them and report the same problems, for example selected Policy Pack baseline differs from rootform.lock. Repair only the affected family from verified local or installed bytes:
rootform vendor dialects --offlinevendor preserves the lock. If verified bytes are unavailable on this machine, prepare them in a connected environment and transfer the complete vendor family. External content storage explains precedence.
A tag needs a registry lookup. With --offline or ROOTFORM_OFFLINE=1, add and update stop with status 2 and "<reference>" is a tag, which cannot be resolved offline. The hint names the setting that enabled offline mode: rerun without --offline, or unset ROOTFORM_OFFLINE when the environment set it. Offline, use a reviewed local source directory or an exact digest reference already installed on this machine instead. Add external content shows both forms.
An external Dialect named like an embedded one, such as aws, never replaces it by accident. add stops with aws is an embedded Dialect; adding another aws replaces it and suggests --replace. Rerun with rootform add dialects <source> --replace only when replacement is intended, after reviewing which embedded Rules the project loses. The reserved rf vocabulary cannot be replaced. See Replace or exclude an embedded Dialect.
add, remove, update, and vendor prepare the new lock in rootform.lock.new. If one was interrupted, the next of these commands stops with rootform.lock.new exists: another rootform add, remove, update, or vendor is running, or one was interrupted. Analysis still reads the committed lock. Confirm that no Rootform command is running, delete the leftover file, then retry. If vendored content no longer matches the lock, repair the affected family with rootform vendor. Who writes this file lists every writer.
Only explicit acquisition or publication crosses that boundary; normal run does not fetch packages. Check the exact OCI reference and digest in the lock, the registry host, DOCKER_CONFIG, credential-helper availability, and SSL_CERT_FILE for a private CA. Do not print credentials while diagnosing. An offline tag lookup cannot discover a new digest: use reviewed local source or an exact digest already installed, or perform selection while connected. Registry compatibility and the OCI mirror procedure give the details.
Selecting a Policy without a Policy Pack reports Error: no Policy Pack is selected (Code: POLICY_UNAVAILABLE) and suggests how to add one. Select a reviewed Policy Pack, then run rootform check. A Policy with no targets contributes no decision. When no selected Policy has a target, the summary reports Evaluations 0 and Verdict NO DECISION, then exits 3. A passing Policy does not offset another Policy with no target; the overall status is NO DECISION unless a violation or indeterminate result takes priority. Inspect the target with rootform show policy <identifier> and compare it with the Form's interpreted Concepts and Rules. Zero evaluations are not compliance. Target scope is exact explains matching, and Understand Policy outcomes shows target coverage.
A violation exits 1; an indeterminate outcome exits 3. SARIF names each violated or indeterminate target; policy.md shows at most ten per outcome unless written with --details. Inspect the proof shows how rootform explain policy <policy> --result <file> reads the recorded outcome and rootform explain instance <address> --input <input> traces its facts and closures. Unknown, sensitive, and unverified absence cannot prove a negative assertion. Resolve input evidence or correct the Policy; do not remove a diagnostic to make the job pass. Policy outcomes explains the three results.
Check both selected stages and the comparison's comparable, problems, and indeterminate entries. A plan defaults to planned; a state JSON has only recorded. run --diff compares separate inputs and is not drift. Different Dialect selections, withheld external identity, or indeterminate closures may prevent a no-change claim. Use comparable stages and read the comparison. Indeterminate preserves uncertainty explains why an unsettled closure never counts as no change.
Each --diff operand must be accepted on its own. A refused input stops the run with status 3; Rootform never treats it as an empty side. A comparison Form reopens with rootform run comparison.json and is refused as a --diff operand with status 2. The refusals above apply to both inputs. A saved Form written in another format is refused with Error: document format "9" is not supported; this build reads format 1 (Code: DOCUMENT_FORMAT_UNSUPPORTED); save it again from its original input with the current binary. For any other rejected Form, run rootform validate form <file>: it names each problem and exits 1. validate form reads only Forms; given a plan JSON, it exits 3 and suggests saving one first. Valid partial Form differs from invalid Form separates missing knowledge from an invalid file.
An occupied port returns status 4 with Error: port <number> is unavailable (Code: SERVER_FAILED) and suggests --port 0 or --no-serve. Choose an available local port with --no-browser --port 0, then open the printed loopback address yourself. --no-serve skips the interface and writes requested files. A browser launch failure does not require rerunning analysis. Stop a foreground server with Ctrl+C. Rootform binds loopback only; use a self-contained HTML export for remote review instead of exposing the local server.
If an output resolves to an input, the command exits 2 with rootform: output "plan.json" resolves to an input. Choose a different path, including when a link points to the input. An unwritable target exits 4; already written files may remain after a later output fails. Check standard error, the command's exit status, and directory ownership. In a container, UID/GID 65532:65532 must be able to write the report mount; container mounts explain the split.
If the symptom remains, report a synthetic reproduction without credentials, raw plans, state, or private infrastructure.