Initializing Enclave...

How to Fix Terraform 'The condition expression must be boolean' Error

Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5 mins

TL;DR

  • What broke: A condition block inside a validation, precondition, or postcondition block is returning a non-boolean value — Terraform requires a strict true/false result, nothing else.
  • How to fix it: Wrap the offending expression with a boolean-coercing function like can(), contains(), length(...) > 0, or a direct comparison operator so the expression evaluates to true or false.
  • Fast path: Drop your failing .tf file into the Client-Side Sandbox above to auto-refactor the condition expression without sending your config anywhere.

The Incident (What Does the Error Mean?)

Raw error output:

Error: The condition expression must be boolean

  on main.tf line 12, in variable "instance_type":
  12:       condition = var.instance_type

The condition expression must return a boolean (true or false) value, not a string.

Terraform's validation and lifecycle condition system is strict: the condition argument accepts exactly one boolean expression. If your expression resolves to a string, number, list, object, tuple, or null, Terraform aborts at plan time — before a single resource is created, modified, or destroyed. Every terraform plan and terraform apply in your pipeline is dead until this is resolved.


The Attack Vector / Blast Radius

This error surfaces in three distinct Terraform constructs, each with its own blast radius:

1. variable validation blocks — Broken immediately on terraform plan. No infrastructure runs. CI/CD pipeline fails at the gate.

2. precondition blocks (Terraform 1.2+) — Resource provisioning is blocked. In a module chain, this can cascade: a failed precondition on a data source or resource prevents all dependent resources in the graph from being evaluated.

3. postcondition blocks — The resource has already been provisioned by the time this fires. A non-boolean postcondition crashes the apply mid-run, leaving infrastructure in a partial deployment state — the worst possible outcome in production.

The secondary risk: engineers under pressure will comment out the condition entirely to unblock the pipeline. That removes your guardrail permanently and the underlying misconfiguration ships to prod silently.


How to Fix It (The Solution)

Basic Fix — Direct Boolean Comparison

The most common cause: passing a variable or expression directly instead of evaluating it.

variable "instance_type" {
  type = string

  validation {
-   condition     = var.instance_type
+   condition     = contains(["t3.micro", "t3.small", "m5.large"], var.instance_type)
    error_message = "instance_type must be one of: t3.micro, t3.small, m5.large."
  }
}

Common Non-Boolean Patterns and Their Fixes

# Pattern 1: length() returns a number, not a boolean
- condition = length(var.allowed_cidrs)
+ condition = length(var.allowed_cidrs) > 0

# Pattern 2: regex() returns a string match, not a boolean
- condition = regex("^arn:aws:", var.role_arn)
+ condition = can(regex("^arn:aws:", var.role_arn))

# Pattern 3: try() returns the value, not a boolean
- condition = try(var.tags["Environment"], null)
+ condition = try(var.tags["Environment"], null) != null

# Pattern 4: lookup() returns a string
- condition = lookup(var.config, "enabled", "false")
+ condition = lookup(var.config, "enabled", "false") == "true"

Enterprise Best Practice — Precondition with can() and alltrue()

In complex modules, validate multiple constraints atomically. Use alltrue() to compose boolean checks and can() to safely probe expressions that might throw errors:

resource "aws_instance" "app" {
  ami           = var.ami_id
  instance_type = var.instance_type

  lifecycle {
    precondition {
-     condition     = var.ami_id && var.instance_type
+     condition     = alltrue([
+       can(regex("^ami-[0-9a-f]{8,17}$", var.ami_id)),
+       contains(["t3.micro", "t3.small", "m5.large"], var.instance_type)
+     ])
      error_message = "ami_id must be a valid AMI ID and instance_type must be an approved size."
    }
  }
}

Key functions for boolean-safe conditions:

Function Use Case
can(expr) Returns true if expr evaluates without error, false otherwise
contains(list, val) Membership check — always boolean
alltrue(list) AND across multiple boolean expressions
anytrue(list) OR across multiple boolean expressions
startswith(str, prefix) String prefix check — always boolean (Terraform 1.3+)
endswith(str, suffix) String suffix check — always boolean (Terraform 1.3+)

💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

This error should never reach a human engineer's terminal. Catch it in the pipeline.

1. terraform validate as a pre-commit hook

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.83.5
    hooks:
      - id: terraform_validate

terraform validate catches boolean type mismatches before any plan is run. Zero cost. Zero excuse not to have it.

2. TFLint with the AWS ruleset

tflint --init
tflint --call-module-type=all

TFLint performs static type analysis and will flag expressions in condition blocks that cannot resolve to boolean.

3. Checkov policy scan

checkov -d . --framework terraform

Checkov's Terraform runner validates HCL structure including validation block integrity as part of its static analysis pass.

4. OPA/Conftest for custom boolean expression enforcement

# policy/condition_must_be_bool.rego
package terraform.validation

deny[msg] {
  block := input.resource.aws_instance[_].lifecycle[_].precondition[_]
  not is_boolean(block.condition)
  msg := sprintf("precondition condition must be a boolean expression, got: %v", [block.condition])
}

Enforce this in your Atlantis or Spacelift pipeline as a mandatory policy gate. Merge is blocked if the condition expression type cannot be statically verified as boolean.

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →