{"record":{"id":"0448f3fdae487320","repo":"hashicorp/terraform","slug":"operation-canceled","errorCode":null,"errorMessage":"operation canceled","messagePattern":"operation canceled","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/command/meta.go","lineNumber":530,"sourceCode":"\t\t// Notify the user\n\t\topReq.View.Interrupted()\n\n\t\t// Still get the result, since there is still one\n\t\tselect {\n\t\tcase <-m.ShutdownCh:\n\t\t\topReq.View.FatalInterrupt()\n\n\t\t\t// cancel the operation completely\n\t\t\top.Cancel()\n\n\t\t\t// the operation should return asap\n\t\t\t// but timeout just in case\n\t\t\tselect {\n\t\t\tcase <-op.Done():\n\t\t\tcase <-time.After(5 * time.Second):\n\t\t\t}\n\n\t\t\treturn nil, errors.New(\"operation canceled\")\n\n\t\tcase <-op.Done():\n\t\t\t// operation completed after Stop\n\t\t}\n\tcase <-op.Done():\n\t\t// operation completed normally\n\t}\n\n\treturn op, nil\n}\n\n// contextOpts returns the options to use to initialize a Terraform\n// context with the settings from this Meta.\nfunc (m *Meta) contextOpts() (*terraform.ContextOpts, error) {\n\tworkspace, err := m.Workspace()\n\tif err != nil {\n\t\treturn nil, err\n\t}","sourceCodeStart":512,"sourceCodeEnd":548,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/command/meta.go#L512-L548","documentation":"Returned from `Meta.runOperation` (the operation runner in `meta.go`) when the user sends a **second** interrupt signal (e.g. a second Ctrl+C) after a graceful `Stop()` was already requested. The first signal triggers graceful stop; the second escalates to `FatalInterrupt()` and `op.Cancel()`, then this error is returned after at most a 5-second grace window for the operation to drain.","triggerScenarios":"Two signals arrive on `m.ShutdownCh` before `op.Done()` fires: the inner `select` catches the second `<-m.ShutdownCh`, calls `FatalInterrupt` + `Cancel`, waits up to 5s for `op.Done()`, and returns `errors.New(\"operation canceled\")`.","commonSituations":"User impatiently hits Ctrl+C twice during `terraform apply` / `plan` / `destroy`; long-running provider operations that do not respond quickly to the first interrupt; SIGINT delivered by orchestration that double-sends.","solutions":["Send the interrupt only once and wait for graceful stop; the operation should finish or print its own interrupt diagnostics.","If the operation genuinely hangs past the 5s grace window, file a bug against the provider that is not honoring context cancellation.","In automation, send a single SIGTERM/SIGINT and rely on Terraform's own graceful shutdown rather than escalating."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"// Distinguish a hard cancel from a normal operation result:\nfunc isHardCancel(err error) bool {\n    return err != nil && err.Error() == \"operation canceled\"\n}","tryCatchPattern":"op, err := meta.runOperation(...)\nif err != nil && err.Error() == \"operation canceled\" {\n    // user sent a second SIGINT; exit without treating as a Terraform failure\n    return exitCodeFromFatalInterrupt()\n}","preventionTips":["In automation, send exactly one interrupt and wait for graceful shutdown.","Do not conflate 'operation canceled' with apply/plan failures in exit-code mapping.","Surface fatal-interrupt UX distinctly from normal command errors."],"tags":["interrupt","signal-handling","operation-lifecycle","terraform-cli"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}