Repository navigation
Description
🤖 Generated by the Agentic Engineer
Impact
Every production deploy has failed since 12:14Z, so nothing can merge in this repository. main has not advanced since 06:36Z. Each merge-group candidate fails its deploy, is evicted from the queue, and the next one hits the identical failure — independent of what that PR changes.
What is stuck
PersistentVolumeClaim/headlamp/headlamp is in Terminating and cannot leave that state:
deletionTimestamp: 2026-08-16T12:14:33Zfinalizers: [kubernetes.io/pvc-protection]- pod
headlamp-79658d4bd5-4pmgk(Running, 11d) still mounts it as volumeplugins-dir
pvc-protection blocks deletion while a pod uses the claim, and the pod is not going anywhere on its own. So the claim can neither finish deleting nor return to a healthy state.
The apps layer waits on it and times out every time:
health check failed after 20m0.036985668s:
timeout waiting for: [PersistentVolumeClaim/headlamp/headlamp status: 'Terminating']
flux-system/apps sits at Ready=Unknown, which is what fails the deploy's 🔁 Trigger Flux reconciliation step. Every other Flux Kustomization is Ready; all HelmReleases are Ready and all Canaries Succeeded.
Root cause
#2741 deletes k8s/bases/apps/headlamp/persistent-volume-claim.yaml. Its speculative merge-group deploy applied that removal to the real production cluster at 12:14:33Z, so Flux pruned the claim. The prune could not complete because of the mounted pod, the deploy failed, and #2741 was evicted without merging.
The deletion request survives the eviction. main still declares the PVC (256Mi), so Flux keeps trying to reconcile a claim that is Terminating and can never become Ready.
🩹 Heal Prod cannot repair this: re-deploying main re-declares the PVC, but nothing can un-delete an object that already carries a deletionTimestamp. That is why the heal job fails too, and why this does not self-heal.
Remediation (needs a cluster mutation)
Deleting the pod releases the finalizer, lets the claim finish deleting, and lets Flux recreate it from main:
kubectl --context admin@prod -n headlamp delete pod headlamp-79658d4bd5-4pmgkThen confirm the claim is recreated and Bound, and that flux-system/apps reaches Ready=True.
The only data lost is the 256Mi plugins-dir cache, which is repopulated on start — and #2741 is removing plugin downloads regardless.
The underlying defect
The remediation above unblocks the queue; it does not stop this recurring. A speculative merge-group deploy can perform an irreversible destructive mutation on production for a change that is never merged, and the damage then blocks every unrelated PR. A resource deletion is not speculative in the way a queue assumes.
Worth treating as its own hardening item: either hold destructive prunes out of speculative deploys, or make the heal path able to recover from a prune that cannot complete.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Projects
- StatusShow more project fields👀 In Review
Activity
coderabbitai commentedon Aug 16, 2026
🔗 Related PRs
#2707 - fix(unifi): prevent pruning managed network resources [merged]
#2985 - fix(k8s): gate deploy health on a serving CNPG cluster instead of a fully-ready one [merged]
#3034 - feat(ci): make an orphaned GHCR deploy fence diagnosable and recoverable [merged]
#3077 - fix(ci): reclaim a drain fence whose owning transaction is provably gone [merged]
#3109 - fix(deploy): avoid redundant GHCR node reboots [merged]
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 Issue enrichment is currently in open beta.
You can configure auto-planning by selecting labels in the issue_enrichment configuration.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
devantler commentedon Aug 16, 2026
Update — the stuck deletion has cleared; the remaining state is self-healing. No further prod surgery is needed.
Live state at 15:02Z:
PersistentVolumeClaim/headlamp/headlamp— gone. TheTerminatingstate that jammed the prune has finished, so the original blocker in this issue is resolved.pod/headlamp-79658d4bd5-h7bp6— recreated,Pending. It cannot schedule yet:0/9 nodes are available: persistentvolumeclaim "headlamp" not found.Kustomization/apps— stillReady=Unknown, running health checks forlatest@sha256:9c10930cwith the 20m timeout. That check started 14:47:36Z, i.e. while the PVC was still terminating, so it is doomed and will expire ~15:07Z.Why this now recovers on its own, rather than needing another manual step:
mainstill declares the PVC —k8s/bases/apps/headlamp/persistent-volume-claim.yamlis present. The PR that removes it (fix(headlamp): disable unverified plugin downloads #2741) was evicted, never merged, so the desired state still contains it.headlamp_headlamp__PersistentVolumeClaimis in theappsKustomization's inventory, andprune: true.retryInterval: 2m.So the sequence is: current health check times out ~15:07:36Z → retry ~15:09:36Z → the PVC is re-applied against a namespace where nothing is terminating any more → it binds → the pod schedules →
appsgoesReady=True→ the merge queue stops evicting.The earlier apply could not do this because at 14:47:36Z the PVC was still
Terminating; you cannot recreate an object mid-deletion.Queue state: the merge queue is currently empty — platform#2732 was evicted by this, not by any fault of its own, and will need re-enqueuing once
appsis green.One consequence worth stating plainly: the PV backing the old PVC is gone, so headlamp's
plugins-dirvolume comes back empty. That is a cache directory repopulated on start, not durable state, so no data loss follows — but it is a real effect of the prune rather than a no-op.I am verifying the recovery rather than assuming it, and will post the outcome. If
appshas not goneReady=Trueby ~15:15Z, the self-heal reasoning above is wrong and this needs a second look.devantler commentedon Aug 16, 2026
Resolved — verified recovered.
Live state at 15:03:50Z:
PersistentVolumeClaim/headlamp/headlamp—Bound, freshly provisioned (pvc-a6fc2814-74d6-4728-84ff-842128840a8c, 256Mi,longhorn-wffc), 47s old.Kustomization/apps—Ready=True,Applied revision: latest@sha256:9c10930c…. It had beenUnknown/health-checking for the whole wedge.pod/headlamp-79658d4bd5-h7bp6— movedPending→ContainerCreatingonce the claim bound.The recovery mechanism was as described in my previous comment:
mainstill declares the PVC, Flux still heldheadlamp_headlamp__PersistentVolumeClaimin theappsinventory, so once nothing was mid-deletion the next apply recreated it.One correction to that comment: I predicted the doomed health check would have to expire at ~15:07:36Z with recovery following at ~15:09:36Z. It actually recovered at 15:03:14Z, several minutes before that deadline — so the reconcile did not wait out the full 20m timeout. The mechanism was right; the timing prediction was wrong, and the wedge cleared sooner than stated.
Consequences now unblocked:
appshealth check. platform#3166 (the separate umami ServiceAccount fault) is enqueued at position 1 at its evaluated head.Worth carrying forward, since the trigger is still present: the underlying hazard is unchanged. platform#2741 removes the headlamp PVC manifest, and its speculative
merge_groupdeploy is what pruned the live PVC while a pod still mounted it — the deletion outlived the evicted PR. Merging #2741 for real will delete that PVC again, and if the headlamp pod is still mounting it the prune can jam the same way. That PR should drain the volume's consumer first, or accept a deliberate pod restart, rather than being merged as an ordinary manifest removal.Closing this as resolved.
devantler commentedon Aug 16, 2026
Resolved end to end.
Root cause was the speculative #2741 merge-group artifact removing the live
headlamp/headlampPVC while the old two-container Headlamp pod still mounted it. The deletion timestamp survived that candidate’s eviction, so every later apps reconciliation waited on the samepvc-protectionfinalizer.Recovery and permanent controls:
cdababde7bac37d1c5b001195629532e3574bd3b, adding two-stage retirement protection and static plus live production gates before artifact publication.027b9baea1dbb6036ab3e74492bfe470ec9c312cafter merge-group CI run 31959207601 passed the live gate and production deploy.sha256:7008993dc3cdc31151aaddd8228edf7c4af2ecc32c45e4993e45708e4ffcf451is Ready across all Flux layers.headlamp/headlampclaim. Its PV and Longhorn volume are also gone; only the disposable 256Mi plugin cache was retired.Final state: Headlamp
1/1 Running, HelmRelease Ready/TestSucceeded, all Flux Kustomizations Ready, and post-merge Validate Main, Release, and CodeQL workflows green.