DEV Community

Engr.Hamza
Engr.Hamza

Posted on

How a Missing TTY Silent-Killed Our CI Pipeline for Three Hours

Cover Image

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
Enter fullscreen mode Exit fullscreen mode

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

  1. 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
Enter fullscreen mode Exit fullscreen mode

Configuring TF_IN_AUTOMATION and TF_INPUT ensures the Terraform CLI immediately halts if any unexpected prompt is triggered.

  1. Next, we configure the terraform init and terraform plan steps 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
Enter fullscreen mode Exit fullscreen mode

Running plan with -out=tfplan creates a deterministic execution artifact while -input=false prevents prompt-based stalls.

  1. Finally, we execute the plan file securely using -auto-approve passed 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
Enter fullscreen mode Exit fullscreen mode

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 apply without 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=false on terraform 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=true in your pipeline base environment variables.
  • Do this: Set explicit timeout-minutes on all Terraform CI pipeline jobs.
  • Never do this: Run raw terraform apply -auto-approve without pointing to a pre-generated tfplan artifact.

Key Takeaways

  • Non-Interactive by Default: Force -input=false across all init, plan, and apply steps.
  • Deterministic Plans: Always pass a generated plan file (tfplan) into terraform apply.
  • Fail Fast: Use job-level timeouts and TF_IN_AUTOMATION=1 so 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)