How a Missing TTY Silent-Killed Our CI Pipeline for Three Hours
You hit git push, your automated Terraform pipeline kicks off, and you head out for lunch expecting green checkmarks. Three hours later, Slack alerts start firing because your runner timed out after burning through precious CI build credits.
The Problem Everyone Ignores
When you run terraform apply on your local terminal, Terraform relies on an interactive TTY (teletypewriter) session. If a provider encounters a prompt, an unhandled prompt for input, or an interactive approval step, it waits patiently for human keyboard input.
In a continuous integration environment like GitHub Actions or GitLab CI, there is no human behind the keyboard. Without explicit flags forcing non-interactive behavior, Terraform won't throw an immediate error—it simply halts execution and waits endlessly for input that will never come.
What Actually Works
The fix requires explicitly disabling interactive prompts and forcing Terraform to execute deterministically via environment variables and strict CLI flags. By configuring standard non-interactive flags alongside TF_IN_AUTOMATION, Terraform immediately fails fast whenever input is requested instead of hanging indefinitely.
Here is a robust, production-ready Bash script that wraps Terraform commands for non-interactive CI execution:
#!/usr/bin/env bash
set -euo pipefail
# Force Terraform into strict non-interactive mode
export TF_IN_AUTOMATION=1
export TF_INPUT=0
echo "==> Running Terraform Initialization..."
terraform init -input=false -no-color
echo "==> Planning Terraform Execution..."
terraform plan -input=false -no-color -out=tfplan
echo "==> Applying Terraform Plan..."
terraform apply -input=false -no-color -auto-approve tfplan
This wrapper script guarantees that any step attempting to read user input fails instantly with an actionable exit code, saving hours of wasted CI build time.
Step-by-Step: Let's Build It Together
- First, we need to enforce non-interactive environment settings in our CI environment configuration. This prevents Terraform and underlying cloud SDKs from prompting for terminal confirmations.
# .github/workflows/terraform.yml
name: "Terraform Automation"
on:
push:
branches: [ "main" ]
jobs:
terraform:
runs-on: ubuntu-latest
env:
TF_IN_AUTOMATION: "true"
TF_INPUT: "0"
steps:
- name: Checkout Code
uses: actions/checkout@v4
Configuring TF_IN_AUTOMATION and TF_INPUT ensures the Terraform CLI immediately halts if any unexpected prompt is triggered.
- Next, we configure the
terraform initandterraform plansteps to run with strict output capture and zero interactivity.
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init -input=false -no-color
- name: Terraform Plan
id: plan
run: terraform plan -input=false -no-color -out=tfplan
Running plan with -out=tfplan creates a deterministic execution artifact while -input=false prevents prompt-based stalls.
- Finally, we execute the plan file securely using
-auto-approvepassed against the generated plan file rather than raw code directories.
- name: Terraform Apply
if: github.ref == 'refs/heads/main'
run: terraform apply -input=false -no-color -auto-approve tfplan
Applying the saved plan file guarantees that only the exact reviewed changes are executed in production without waiting for manual CLI confirmation.
The Mistakes That Will Burn You
-
Mistake 1: Running
terraform applywithout an explicit plan file. This causes Terraform to recalculate state on the fly, leading to potential race conditions and unexpected prompt requests. -
Mistake 2: Forgetting
-input=falseonterraform init. When backend configurations or provider modules drift, init will hang while asking whether to copy existing state. - Mistake 3: Omitting pipeline execution timeouts. Without job-level timeouts, a rogue hanging process can consume your entire monthly CI runner budget in a single run.
Production Checklist
-
Do this: Always set
TF_IN_AUTOMATION=truein your pipeline base environment variables. -
Do this: Set explicit
timeout-minuteson all Terraform CI pipeline jobs. -
Never do this: Run raw
terraform apply -auto-approvewithout pointing to a pre-generatedtfplanartifact.
Key Takeaways
-
Non-Interactive by Default: Force
-input=falseacross allinit,plan, andapplysteps. -
Deterministic Plans: Always pass a generated plan file (
tfplan) intoterraform apply. -
Fail Fast: Use job-level timeouts and
TF_IN_AUTOMATION=1so hanging steps fail instantly instead of sitting idle for hours.
Engr. Hamza | AI & MLOps Engineer | Building autonomous systems at the edge of possibility

Top comments (0)