{"record":{"id":"01b22dcc6e05a298","repo":"hashicorp/terraform","slug":"approved-using-the-ui-or-api","errorCode":null,"errorMessage":"approved using the UI or API","messagePattern":"approved using the UI or API","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"internal/backend/remote/backend_common.go","lineNumber":28,"sourceCode":"\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\n\t\"github.com/hashicorp/terraform/internal/backend/backendrun\"\n\t\"github.com/hashicorp/terraform/internal/logging\"\n\t\"github.com/hashicorp/terraform/internal/plans\"\n\t\"github.com/hashicorp/terraform/internal/terraform\"\n)\n\nvar (\n\terrApplyDiscarded   = errors.New(\"Apply discarded.\")\n\terrDestroyDiscarded = errors.New(\"Destroy discarded.\")\n\terrRunApproved      = errors.New(\"approved using the UI or API\")\n\terrRunDiscarded     = errors.New(\"discarded using the UI or API\")\n\terrRunOverridden    = errors.New(\"overridden using the UI or API\")\n)\n\nvar (\n\tbackoffMin = 1000.0\n\tbackoffMax = 3000.0\n\n\trunPollInterval = 3 * time.Second\n)\n\n// backoff will perform exponential backoff based on the iteration and\n// limited by the provided min and max (in milliseconds) durations.\nfunc backoff(min, max float64, iter int) time.Duration {\n\tbackoff := math.Pow(2, float64(iter)/5) * min\n\tif backoff > max {\n\t\tbackoff = max\n\t}","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote/backend_common.go#L10-L46","documentation":"Sentinel error used by the legacy 'remote' backend during the interactive confirmation loop. While the CLI blocks waiting for the operator to type the confirmation keyword, a goroutine concurrently polls the TFE/HCP run; when the run's Actions.IsConfirmable flips false and the status is not RunDiscarded, the run was approved out-of-band (web UI or API), so this sentinel tells the caller to skip issuing its own Runs.Apply since the run is already approved.","triggerScenarios":"Running 'terraform apply' against a 'remote' backend workspace with auto_apply disabled; while the CLI shows the 'yes' prompt, another user or automation approves the same run via the TFE/HCP UI or POST /runs/{id}/actions/apply before the operator confirms locally.","commonSituations":"Multiple engineers watching one workspace; a CI pipeline and a human both acting on the same run; an automated approval webhook firing while someone sits at the prompt.","solutions":["No fix needed — this is informational; the CLI simply proceeds to wait for the apply to complete and reports it was approved elsewhere.","If out-of-band approval is unexpected, audit workspace members and review the run activity log in the TFE/HCP UI.","Enable Auto-Apply on the workspace to remove the interactive confirmation step and avoid the race entirely."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// When consuming the remote backend's confirm result, distinguish the\n// 'approved elsewhere' sentinel from real failures:\nif err := b.confirm(ctx, op, opts, run, \"yes\"); err != nil {\n    if errors.Is(err, errRunApproved) {\n        // already approved via UI/API — proceed to waitForRun, do not call Apply\n        return b.waitForRun(ctx, cancelCtx, op, \"apply\", run, w)\n    }\n    return run, err // genuine error\n}","preventionTips":["Enable Auto-Apply on workspaces to avoid interactive confirmation races.","Communicate run ownership so only one actor approves a given run.","Treat errRunApproved/errors.Is as control flow, never as a hard failure."],"tags":["terraform","remote-backend","tfe","hcp","run-lifecycle"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}