{"record":{"id":"0416964dd20c4b69","repo":"cloudflare/cloudflared","slug":"error-deleting-tunnel-s","errorCode":null,"errorMessage":"Error deleting tunnel %s","messagePattern":"Error deleting tunnel (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/cloudflared/tunnel/subcommand_context.go","lineNumber":231,"sourceCode":"\n\tclient, err := sc.client()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, id := range tunnelIDs {\n\t\ttunnel, err := client.GetTunnel(id)\n\t\tif err != nil {\n\t\t\treturn errors.Wrapf(err, \"Can't get tunnel information. Please check tunnel id: %s\", id)\n\t\t}\n\n\t\t// Check if tunnel DeletedAt field has already been set\n\t\tif !tunnel.DeletedAt.IsZero() {\n\t\t\treturn fmt.Errorf(\"Tunnel %s has already been deleted\", tunnel.ID)\n\t\t}\n\n\t\tif err := client.DeleteTunnel(tunnel.ID, forceFlagSet); err != nil {\n\t\t\treturn errors.Wrapf(err, \"Error deleting tunnel %s\", tunnel.ID)\n\t\t}\n\n\t\tcredFinder := sc.credentialFinder(id)\n\t\tif tunnelCredentialsPath, err := credFinder.Path(); err == nil {\n\t\t\tif err = os.Remove(tunnelCredentialsPath); err != nil {\n\t\t\t\tsc.log.Info().Msgf(\"Tunnel %v was deleted, but we could not remove its credentials file  %s: %s. Consider deleting this file manually.\", id, tunnelCredentialsPath, err)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// findCredentials will choose the right way to find the credentials file, find it,\n// and add the TunnelID into any old credentials (generated before TUN-3581 added the `TunnelID`\n// field to credentials files)\nfunc (sc *subcommandContext) findCredentials(tunnelID uuid.UUID) (connection.Credentials, error) {\n\tvar credentials connection.Credentials\n\tvar err error","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/cmd/cloudflared/tunnel/subcommand_context.go#L213-L249","documentation":"cloudflared wraps a failure from `client.DeleteTunnel(tunnel.ID, forceFlagSet)` — the tunnel was found (GetTunnel succeeded and it is not already deleted) but the actual DELETE request against the Tunnel Store API failed. This is an API-level rejection (permissions, non-deleted tunnel resources, server error), not a lookup failure.","triggerScenarios":"`cloudflared tunnel delete <id>` where the API DELETE returns an error: token/cert lacks delete permission, tunnel still has active connections or route bindings without --force, or a 5xx from the API.","commonSituations":"Deleting a tunnel that still has remnant connections; using a cert.pem whose service token lacks tunnel delete scope; transient Cloudflare API outage; calling delete with --force missing when connections are still registered.","solutions":["Retry with `cloudflared tunnel delete --force <id>` if active connections or bound resources block deletion","Check the tunnel's state with `cloudflared tunnel info <id>` and clean up connections/routes first","Verify origin cert credentials have permission to delete tunnels (`cloudflared tunnel login` again if stale)","Retry after a transient API outage (check https://www.cloudflarestatus.com)","Inspect the wrapped error text for the specific API status code and act on it"],"exampleFix":"// before\nif err := client.DeleteTunnel(tunnel.ID, forceFlagSet); err != nil {\n    return errors.Wrapf(err, \"Error deleting tunnel %s\", tunnel.ID)\n}\n// after (callers run: cloudflared tunnel delete --force <id>)\n// CLI side:\n//   cloudflared tunnel delete --force <id>\n// or in code, surface the force hint:\nif err := client.DeleteTunnel(tunnel.ID, forceFlagSet); err != nil {\n    return errors.Wrapf(err, \"Error deleting tunnel %s (try --force if connections are still active)\", tunnel.ID)\n}","handlingStrategy":"retry","validationCode":"info, err := exec.Command(\"cloudflared\", \"tunnel\", \"info\", tunnelID).Output()\nif err != nil { /* tunnel unreachable/unknown */ }\n// decide if --force is needed based on active connections in info output","typeGuard":null,"tryCatchPattern":"err := run(\"cloudflared\", \"tunnel\", \"delete\", tunnelID)\nif err != nil && strings.Contains(err.Error(), \"Error deleting tunnel\") {\n    // retry with --force or after backoff\n    _ = run(\"cloudflared\", \"tunnel\", \"delete\", \"--force\", tunnelID)\n}","preventionTips":["Use --force when the tunnel may still have live connections","Ensure the cert's service token has tunnel:delete scope","Retry deletes on 5xx with exponential backoff","Verify with `tunnel info` that no active connectors remain before deleting"],"tags":["go","cli","cloudflare-tunnel","api-client"],"backgroundTag":"http-error-response","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}