hashicorp/terraform · error

Error asking

Error message

Error asking %s: %v

What it means

Returned inside the confirm helper when op.UIIn.Input returns a non-nil error that is neither context.Canceled nor a parent-cancellation. The %s is opts.Id (e.g. 'override', 'approval'); the %v is the underlying input error. It indicates the interactive input subsystem itself failed.

Solutions

  1. Run the command in a real TTY, or pass '-auto-approve' / '-input=false' for non-interactive contexts.
  2. Inspect the wrapped error (%v) for the specific input cause (EOF, adapter error).
  3. Ensure stdin is connected and not prematurely closed in CI.
  4. Validate any custom UIIn adapter returns nil on successful input.

Example fix

# before (fails in CI - no TTY)
terraform apply
# after (non-interactive)
terraform apply -auto-approve
Defensive patterns

Strategy: validation

Validate before calling

// Detect non-interactive contexts up front and switch to auto-approve.
func shouldAutoApprove(op *backendrun.Operation) bool {
    return op.UIIn == nil || !isTTY()
}

Try / catch

// Distinguish cancellation from genuine input errors.
if _, err := op.UIIn.Input(ctx, opts); err != nil {
    if errors.Is(err, context.Canceled) { return stopCtx.Err() }
    return fmt.Errorf("input for %s failed (%w); run with -auto-approve in non-interactive shells", opts.Id, err)
}

Prevention

When it happens

Trigger: op.UIIn.Input(doneCtx, opts) errors while stopCtx is still alive. Caused by a closed/missing TTY, a broken input adapter, an EOF on stdin, or an internal error in the UI input implementation.

Common situations: Running in a non-interactive shell without -auto-approve; stdin redirected from a file that ended; terminal disconnected; custom UIIn implementation throwing; SSH session dropped mid-prompt.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/98e7a2cac396ffe1. Report an issue: GitHub.

Appendix: source

Thrown at internal/backend/remote/backend_common.go:557

					if err == errRunDiscarded {
						err = errApplyDiscarded
						if op.PlanMode == plans.DestroyMode {
							err = errDestroyDiscarded
						}
					}

					result <- err
					return
				}
			}
		}
	}()

	result <- func() error {
		v, err := op.UIIn.Input(doneCtx, opts)
		if err != nil && err != context.Canceled && stopCtx.Err() != context.Canceled {
			return fmt.Errorf("Error asking %s: %v", opts.Id, err)
		}

		// We return the error of our parent channel as we don't
		// care about the error of the doneCtx which is only used
		// within this function. So if the doneCtx was canceled
		// because stopCtx was canceled, this will properly return
		// a context.Canceled error and otherwise it returns nil.
		if doneCtx.Err() == context.Canceled || stopCtx.Err() == context.Canceled {
			return stopCtx.Err()
		}

		// Make sure we cancel the context here so the loop that
		// checks for external changes to the run is ended before
		// we start to make changes ourselves.
		cancel()

		if v != keyword {
			// Retrieve the run again to get its current status.

View on GitHub (pinned to d32a084675)