Skip to main content

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>.json in 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.