Troubleshooting
Octo fails early by design, so most of what goes wrong stops the run before any Terraform is
written. The messages are precise about what happened and quiet about what to do next: Input "receipts-module.input.region" refers to "${{region-module.model.region}}", which does not exist!
is exactly true and does not say that the fix is usually to move one entry higher up the file.
Below is what each of the common ones means. They are grouped by the step that produced them, because the step narrows the cause further than the message does.
Before the definition file loads
Octo definition file not found at "…"!
Octo reads octo.yaml from the directory you run it in. Run it from your project's root, or name
the file:
OCTO_DEFINITION_FILE_PATH=octo.prod.yaml node_modules/.bin/octo generate -o .octo-tf
Definition file failed validation against schema!
A key is misspelled, missing, or in the wrong place. The failures underneath say where:
FAILURES
========
1. [/terraform] must have required property 'requiredVersion'
2. [/terraform] must NOT have additional properties
Those two lines are one typo. requiredVersionn is both a missing required property and an
unexpected extra one. Put the schema comment at the top of octo.yaml and your editor catches this
before you run anything:
# yaml-language-server: $schema=https://raw.githubusercontent.com/quadnix/octo/refs/heads/main/packages/octo-build/src/resources/octo-v1.schema.json
Key "RECEIPT_BUCKET_NAME" not found under "env"!
A ${env.X} reference with no matching entry in the env block. Usually a typo, and it is an error
rather than an empty string on purpose.
Definition holds a value that contains itself!
A YAML anchor that points back at something it sits inside. Octo walks every value looking for
${env.X} references, and a value holding one of its own ancestors has no end to walk to. Anchors
reused in two separate places are fine; this is the one that nests inside itself.
Invalid module definition for moduleId="region-module": "AwsMultiAzRegionModule" not found in "imports"!
A moduleClass that no imports entry provides. Either add the import, or fix the class name; they
have to match exactly.
Failed to import path "./dist/index.js": ...
For your own modules, this is almost always a build you did not run. npm run build, then try
again. Relative paths resolve against the definition file, not your shell.
While modules compose
Module "receipts-module" has no input "bucketname"!
A key under moduleInputs is not one the module takes: a typo, or an input a newer version of the
module dropped. Octo refuses it rather than read the input as unset. The module's page under
/sdk lists what it takes.
Input "receipts-module.input.region" refers to "${{region-module.model.region}}", which does not exist!
A ${{...}} reference pointing at something that does not exist yet. Two causes, in order of
likelihood: the module you referenced is listed after the one referencing it, or the moduleId is
misspelled. Modules resolve in the order the definition file lists them, and modules of one class
run together, where the first of them is listed.
Property "region" in schema could not be resolved!
A required input is missing from the module's moduleInputs, and has no default.
Property "region" in schema could not be validated!
The reference resolved, and what came back is the wrong shape. Usually the wrong node, as in handing
a storage module an account where it wants a region. The accompanying schemaClassName tells you
which module's schema rejected it; read that class to see what it accepts.
A ModuleError from a module you are using
For example Cannot find remote directory in S3Storage service!. The module is telling you it
cannot satisfy the request as written, and the message is written for you rather than for a stack
trace. In that case, the directory you asked a server to access is not one the bucket was told to
create.
While Terraform is generated
No provider registered for account "123456789012" and region "us-east-1"!
A module produced resources in an account and region combination with no matching entry under
terraform.providers. Every account and region pair you deploy into needs one; a module that spans
two regions needs two.
Terraform provider alias "prod-eu-west-1" is already taken by account "123456789012"!
Two entries under terraform.providers gave the same accountName for the same region. An alias
has to be unique within a provider type, and a name, unlike the account number it replaces, is
yours to repeat by accident. Rename one of the two.
"TableName" "accounts" is not unique within awsAccountId, awsRegionId: ...
Two resources would reach AWS under one name where AWS allows only one. The message names both
resources and the modules that created them. The usual cause is two Octo regions pointing at the
same AWS region, each with a module creating a table accounts. Rename one, or move it to another
AWS region. Two entries inside one resource, such as two CloudFront cache policies of one name,
produce the same message naming one resource twice.
Resource "..." already exists, and action "..." created it again!
Two actions built a resource with the same ID in the same place in the graph. Octo refuses rather than merging the two. The usual cause is a module loaded twice whose resource ID is not built from its inputs, so both instances name their resource alike; build the ID from the input that tells the instances apart. An action that wants to change a resource another module created calls a method on that resource instead of constructing a second one.
Resource "..." of module "..." takes a name of "..." of module "...", and terraform cannot move infrastructure between module folders!
A resource is gone and a new one of its kind holds its name, so Octo would hand the old one's Terraform state to the new one, but the two live in different module folders and Terraform moves state only within one. Applied as it is, the new resource would be created while the old one still held the name, and the provider would refuse it. Remove the old resource in one apply and add the new one in the next.
Resource "..." takes the names of both "..." and "...", and can take over only one of them!
A new resource holds names that two deleted resources held, and Octo can move only one of them into
it. Resources "..." and "..." both take a name of "...", and only one can take it over! is the
same problem the other way round. Change the names so each new resource takes over at most one
deleted resource, or remove the old ones in one apply and add the new ones in the next.
No matching action given for diff!
A node changed and nothing is registered to handle it. The error carries the diff itself, including
the node and the field, which is normally enough. Two causes: a construct file is not imported into
src/index.ts, so its @Action decorator never ran, or an action's filter() is narrower than the
diffs its node can produce.
A module you loaded generated no folder
Not an error. A module that produces no cloud resources produces no folder, and the shop's app and
account modules are both like this. Check the written: list in the generate output before
concluding something is wrong.
While Terraform plans
FAILURES — octo could not read these, so everything else is incomplete
Terraform could not plan a folder Octo generated, and the whole message from Terraform is underneath. This is the only outcome that exits non-zero, because a report that could not read one folder cannot be trusted about any of them.
The fix is whatever Terraform says: an unsupported argument, a credential problem, a provider version constraint. The generated folders are ordinary Terraform, so you can go into one and debug it directly:
cd .octo-tf/region-module
terraform init -backend=false
terraform validate
No response recorded for resource "..."!
Terraform asked Octo for an external resource's response and neither the apply that would have produced it nor a commit that would have recorded it has happened. During a normal first plan Terraform does not ask, because it can see the resource is about to change. It asks when the resource is settled, so this usually means state that was restored, moved, or partly lost. Applying and committing puts the response back.
External resource "..." is published but not recorded in octo state
An external resource is published by an action
that runs in the middle of an apply, and Octo records what that action produced separately until
octo commit folds it into the resource graph. This note says a record is still sitting there, so
a run stopped somewhere between the two.
How the sentence ends decides whether you do anything. Ending in the next successful commit records it means the resource is still declared, so running the lifecycle again is the whole fix: the
commit adopts the record, and your action is not called a second time, so nothing is published
twice.
Ending in so octo will never delete it means the record has been orphaned, and something exists in
your cloud account that no future run will remove. Octo deletes only what it has recorded, and this
was never recorded. It happens when the resource left the definition file, or when its resource ID
changed, before any commit saw the first version - bumping an image tag does exactly that, because
the tag is part of the ID. Two ways out:
- Put the resource back in the definition file as it was and run the lifecycle. The commit adopts the record, and removing it after that deletes it properly.
- Delete the published thing yourself, then delete its record. The record is
resource-state-<resource ID>.jsonin the state directory, with any/in the ID written as..
A plan that fails on the account the credentials belong to
Octo writes allowed_account_ids onto every generated aws provider block, from the accountId in
your definition file. The provider compares it against the account your credentials actually resolve
to and refuses at plan when they differ, so nothing has been created or changed when this fires.
It means the definition and the environment disagree: an AWS_PROFILE naming another account, a
stale SSO session, a CI job that assumed the wrong role, or an accountId read from one environment
variable while the credentials come from another. Fix whichever of the two is wrong and run again.
There is nothing to clean up.
The guard is left off in three cases. When the provider is registered with an accountName, because
that exists to keep the account id out of committed Terraform and the guard is the id. When your
provider spec already sets allowed_account_ids, which wins. And when it sets
skip_requesting_account_id, which stops the provider learning the account there would be to check.
Terraform plan format version "2.0" is not one octo reads (1.x)!
Your Terraform or OpenTofu writes its plan in a shape Octo was not written against, so Octo reports
that folder as failed rather than guess what the plan means. Run a Terraform release Octo supports,
or an Octo release that reads yours. octo apply refuses for the same reason, since it cannot tell
what the plan would destroy.
Everything reads (known after apply) on the first plan
Expected. A folder planned before its dependency exists is planned against Terragrunt's
mock_outputs, which is exactly what an approximate report means. Apply, commit, then validate
again; the second report is exact.
While taking the lock
App already locked with lock ID "...", held for 4h 12m
Another run holds the lock. The message names it and says how long it has been held, which is what
separates a run still working from one that died: seconds mean wait, hours on a job that takes
minutes mean nobody is coming. With RemoteStateProvider the message also says who took it, as
taken by "octo on ci-runner", held for 4h 12m.
A run that died is finished through its lock, never released. The error ends with the command that resumes it, and Recovering a Failed Run has every step.
App is not locked with lock ID "..."!
The lock you tried to resume is not held. Somebody already finished that run; octo generate picks
up what it left.
App state moved since it was last refreshed! Run "octo generate" again.
RemoteStateProvider only. Another run finished after your generate, so the Terraform you
generated was planned against a state that is no longer the newest. The lock was released again
before anything ran. Run generate again, then lock.
If you generate from a branch, bring it up to date with main first. The other run's change is in
main's definition file and not in yours, so a plan from the old branch against the new state
reads as destroying what that run built. In a pipeline, that is requiring a pull request to be up
to date before it applies.
App is not locked!
apply and commit both require the lock. Take it with octo lock and pass the ID it prints to
both. Apply adds Run "octo lock" first. to the same message.
Invalid lock ID!
A lock is held, but not the one you passed. Usually a stale LOCK_ID from a previous run still in
the shell. Run octo lock to see which ID holds it.
Invalid lock ID! Lock "..." was not taken in this state directory. Resume it here first.
RemoteStateProvider only. The lock is held, and was taken on another machine or in another
checkout, so this directory holds none of what that run wrote. Resume it here with
octo lock --lockId first.
State holds the journal of a run that was never archived: journal.1791692481268-593d89e5.commit.json! Move or delete these files, then lock again.
The state directory holds a journal file of
a command that only runs under a lock, and no lock is held, so the file belongs to no run. Either
somebody finished your lock from another machine, with octo lock --lockId, and the files are this
machine's copies of what that lock already uploaded; or, with remote state, the last run released
its lock but could not move its journal into archives/, which commit or unlock reported on
stderr as Failed to archive the state of commit "...". The lock was not taken.
The files are a record and nothing reads them back. Move them anywhere you want to keep them, or delete them, then lock again.
Failed to write the app state: State file "..." is larger than ... bytes!,
Failed to write the app state: An upload may name at most ... files!
RemoteStateProvider only. These are the limits the backend holds your app to — 20MB a file and
1000 files a state unless your app's limits were raised — and the backend refused the write before
any file was uploaded. The files are in your state directory; the run's record holds what it held
before. Journal files count toward both.
Failed to lock the app: App state storage is full!
RemoteStateProvider only. The states your app holds have passed what it may store, 100MB unless
its limit was raised. This is checked only when a lock is taken, so a run already holding a lock
finishes, and the next one is refused. Nothing on the Octo side raises the limit.
While applying
This apply would DESTROY: ...
Working as intended. octo apply plans first and refuses when anything would be destroyed. Read the
list, run octo validate for the reason behind each one, then re-run with --auto-approve and the
same lock ID. A line ending - the data it holds is lost with it destroys a table's rows, a bucket's
objects, a filesystem's files, or a repository's images along with the resource. A line ending
- an action octo does not know, which may destroy it comes from a newer Terraform that plans an
action Octo was not written against; Octo asks rather than assume it is harmless, and octo validate
shows what it changes. The lock is still held, so if you are not going ahead, release it with
octo unlock --lockId.
Cannot check this apply for destroys because the folders above would not plan. Fix them, or pass --auto-approve to apply anyway!
Same guard, different reason: a folder failed to plan, so Octo cannot say whether it would destroy anything, and will not assume. Fix the plan failure.
octo: command not found, from inside an apply
Terraform called back into octo run-action for an external
resource and could not find the binary. Run the
lifecycle through the npm scripts, which put node_modules/.bin on PATH for child processes. The
same callback happens during a validate, where it is octo get-response and the same fix applies.
One folder failed and the others did not
Expected, and the state it leaves you in is a safe one. Terragrunt applies folders in dependency order and stops at a failure: the folders that depend on the failed one are skipped, and the folders that do not still apply. A failed image build leaves the repository created and the service that would run the image untouched, rather than both half done. The failed folder itself may have applied part of what it holds, which is Terraform's own state to reconcile.
Run octo commit --partial to record what did apply. It names every module it left out. Then fix
the failure and run again: the folders that already applied come back as no-ops, the failed one
finishes, and the skipped ones run for the first time.
A validate in that state exits zero, because a skipped folder plans against mock_outputs rather
than against nothing. Read it as approximate until a run completes.
Failed to check terraform state in: ..., just before the apply's own error
The apply failed, and copying Terraform's state into Octo's state afterwards failed too, usually because remote state refused the write. Fix the error the apply ended with, printed right after this line. Nothing is lost: Terraform's state is still in each module folder, and the next command that runs Terraform copies it in.
Could not run "docker build"! or "docker build" exited with 1!, from inside an apply
Publishing a container image is an external resource, and its action runs the local docker CLI on
the machine doing the apply. The first message means the binary is not on PATH or the daemon is
not running; the second carries Docker's own stderr from a build that failed. Nothing is published
either way, so the repository is there and empty, and applying again once the build works publishes
into it with nothing to repair by hand.
While committing
Every commit that fails keeps the lock and leaves Octo state unchanged, so the fix is followed by
the same commit with the same --lockId. Recovering a Failed
Run walks through each case.
Cannot commit: missing terraform outputs (partial apply?)
Some folders never produced their outputs. Usually the apply did not finish: octo commit --partial
records every resource whose outputs all resolved and leaves the rest out, reporting what it
skipped. The next run plans to create those again, which Terraform reconciles against its own state
as a no-op if they exist. If the lines above the error say Could not read terraform outputs, the
apply was fine and the commit could not reach a folder, usually for want of credentials.
Terraform outputs are not in a format octo reads!
terraform output -json returned something other than one object per output holding its value.
As with a plan in an unknown format, run a Terraform release Octo supports.
Cannot commit: null terraform outputs (broken apply?)
A declared output exists but carries no value, which normally means the resource behind it did not come up. Apply again with the same lock, then commit.
Partial commit: N external resource(s) were not deleted because this apply was not confirmed everywhere
A warning, and the intended behaviour. The delete action of an external resource runs at commit, over the whole diff at once, with none of the ordering Terraform destroys its own resources in. That is safe only when the apply reached the desired state everywhere, so an apply that stopped short defers every one of them. The resources stay recorded as present and the message names each. Fix the apply and run again; the next successful run deletes them.
Terraform outputs supplied for folder "X", which octo does not track
A warning, not an error. There is a folder in the output directory that no module in your intent owns, and no committed state claims. Usually a leftover from a module you removed, in which case it is harmless and can be deleted. Occasionally it is a second definition file writing into the same directory, which is worth fixing.
Afterwards
Drift you did not cause
Check the confidence on the report first. Drift on an approximate report can be an artefact of
planning against a placeholder. Get to a state where your intent and infrastructure agree, run
validate again, and see whether it survives. Reading a Validate
Report covers what to do when it does.
Octo wants to create everything, and it all already exists
.octo/ is missing or was not committed. Terraform's state lives in there too, so neither Octo nor
Terraform has a record, and an apply now would build a second copy of everything that has no unique
name, such as a VPC, and fail on everything that has one. Do not apply. Restore the state files,
from version control or wherever your state provider keeps them, and validate again. See
State.
Two people ran it at once
apply and commit require the app lock, so a second run on the same machine fails while taking
it. Two machines are a different matter: the local providers keep that lock as a file beside the
state, and each machine has its own. generate takes no lock at all, so two of those overwrite each
other's model state with no error either way.
Treat the state as suspect: restore it from version control, then run generate, apply, and commit once from one place. Running the lifecycle from a pipeline is what stops it recurring, and remote state, whose lock every machine shares, is what removes the possibility.