hashicorp/terraform · info
approved using the UI or API
Error message
approved using the UI or API
What it means
This is a sentinel returned by the remote backend's confirmation poller (confirm() in backend_common.go:529). When the CLI is waiting for the user to type 'yes' to apply, it periodically re-reads the run; if the run is no longer confirmable (r.Actions.IsConfirmable == false) and was not discarded, it means someone approved the run through the Terraform Cloud / Enterprise web UI or the TFE API. It is informational, not a hard failure: backend_apply.go:237,242 explicitly checks `err != errRunApproved` and uses it to skip the CLI-side Runs.Apply call because approval already happened server-side.
Solutions
- Do nothing — the run is proceeding on the server; the CLI message is a notification, not a failure.
- If you must own approval from the CLI, ensure no other operator/automation approves the run concurrently and disable conflicting auto-apply for that workspace.
- Configure the workspace so only CLI-driven runs are used, or avoid running apply from two places at once.
Defensive patterns
Strategy: try-catch
Type guard
// Detect the 'approved externally' sentinel from the remote backend.
func isRunApprovedExternally(err error) bool {
return errors.Is(err, errRunApproved) // compare to the exported sentinel if exposed, else string-match
} Try / catch
err := b.confirm(ctx, op, opts, r, "yes")
if err != nil {
if errors.Is(err, errRunApproved) {
// Run was approved via UI/API; no local Apply call needed. Treat as success.
return nil
}
return err
} Prevention
- Avoid approving the same run from both the CLI and the UI/API concurrently.
- Branch on the sentinel with errors.Is rather than string comparison.
- Treat errRunApproved as informational, not a failure.
When it happens
Trigger: Calling terraform apply against the 'remote' backend while a workspace run is in the pending-confirmation state, and the run transitions to approved by another channel before the CLI confirms. Specifically the confirm() goroutine (backend_common.go:524-531) returns errRunApproved when keyword=="yes", !r.Actions.IsConfirmable, and r.Status != tfe.RunDiscarded.
Common situations: A teammate clicks 'Confirm & Apply' in the Terraform Cloud UI while your local CLI sits at the apply prompt. An automation script approves the run via POST /runs/:id/actions/apply while the CLI polls. A workspace with mixed approval sources where Auto-apply races the manual prompt.
Related errors
- discarded using the UI or API
- error deleting workspace
- Error downloading state
- error loading variables
- error loading workspace
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/01b22dcc6e05a298.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote/backend_common.go:28
"fmt"
"io"
"math"
"strconv"
"strings"
"time"
tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform/internal/backend/backendrun"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/terraform"
)
var (
errApplyDiscarded = errors.New("Apply discarded.")
errDestroyDiscarded = errors.New("Destroy discarded.")
errRunApproved = errors.New("approved using the UI or API")
errRunDiscarded = errors.New("discarded using the UI or API")
errRunOverridden = errors.New("overridden using the UI or API")
)
var (
backoffMin = 1000.0
backoffMax = 3000.0
runPollInterval = 3 * time.Second
)
// backoff will perform exponential backoff based on the iteration and
// limited by the provided min and max (in milliseconds) durations.
func backoff(min, max float64, iter int) time.Duration {
backoff := math.Pow(2, float64(iter)/5) * min
if backoff > max {
backoff = max
}View on GitHub (pinned to d32a084675)