A safe stuck-Terminating doctor for Kubernetes. It pinpoints the exact finalizer
and the dead controller / APIService blocking deletion, cleans truly-orphaned
resources first, and clears finalizers only as a gated last resort.
kubectl finalizer-doctor (alias: kubectl fid).
Clearing a finalizer is irreversible and can orphan real infrastructure. finalizer-doctor exists to make that decision safely, not to make it easy:
- Dry-run by default. It changes nothing unless you pass
--apply.- It refuses to guess. A finalizer is only "safe to clear" when there is hard proof the owning controller is gone. Anything it cannot read, or any sign the controller is merely slow, blocks the verdict.
- It re-verifies before every irreversible action and binds the
--applyconfirmation to the exact state it showed you.If you just want the namespace gone and don't care what breaks, this is not the tool for you — a three-line snippet will do that, dangerously.
Namespaces (and other objects) hang in Terminating because a finalizer's
controller crashed or was uninstalled. The usual fixes — editing out finalizers,
--grace-period=0 --force, blindly emptying spec.finalizers — orphan real
infrastructure because nobody distinguishes "controller gone, safe to clear"
from "controller just slow." finalizer-doctor makes that distinction explicit
and evidence-based.
brew install alexremn/tap/finalizer-doctorThis installs both kubectl-finalizer_doctor and kubectl-fid on your PATH, so
kubectl finalizer-doctor and kubectl fid work immediately.
Download the archive for your platform from the
releases page and place
both kubectl-finalizer_doctor and kubectl-fid on your PATH. kubectl will
then expose them as kubectl finalizer-doctor and kubectl fid.
kubectl krew install finalizer-doctor# Diagnose a stuck namespace (dry-run, human output)
kubectl finalizer-doctor ns/my-namespace
# Any finalizer-blocked resource
kubectl finalizer-doctor widgets.example.com/foo -n team-a
# Machine-readable output for CI
kubectl finalizer-doctor ns/my-namespace --output json
# Scan the whole cluster for stuck objects (read-only)
kubectl finalizer-doctor --all
# Apply (mutates) — requires the proof-bound digest the dry-run printed
kubectl finalizer-doctor ns/my-namespace --apply --confirm=<digest>--apply cannot be combined with --all.
--verdict strict(default): DEAD only on hard proof, with vetoes for live or unreadable signals. Time alone never yields DEAD.--verdict score: a transparent confidence readout that still requires a hard signal and passes the same vetoes — at least as safe as strict.
| Code | Meaning |
|---|---|
0 |
nothing stuck, or apply cleared cleanly |
1 |
operational error or invalid invocation (e.g. --apply with --all) |
2 |
stuck object(s) found (report-mode signal for CI) |
3 |
refused (SLOW/UNKNOWN, gate not satisfied, blocker, re-verify changed) |
The guardrails between a verdict and a mutation:
- Dry-run by default — nothing changes without
--apply. - Evidence-based verdict — a finalizer is only
DEAD("safe to clear") on hard proof the owner is gone; a live signal, an unreadable liveness source, or time alone never yieldsDEAD. - Proof-bound confirmation —
--apply(non-interactive) requires a--confirmdigest that the dry-run prints, binding the token to the exact target, finalizer set, verdict, andresourceVersion. - Per-action re-verify — the verdict is re-checked immediately before every irreversible action; a recovered controller aborts the run.
- Ordered remediation — clean true orphans first, refuse if a
failurePolicy=Failwebhook with dead backing would reject the patch, then clear the finalizer last. - Targeted patch — removes only the dead finalizer; namespaces use the
/finalizesubresource with aresourceVersionprecondition; the namespacespecandmetadatafinalizers are gated jointly.
See CONTRIBUTING.md. Security reports: SECURITY.md.