A manifest-only regression deadlock suggests the reconciler cache key is missing a manifest digest or treating unchanged source files as sufficient. A good test would change only the manifest, leave source files untouched, and assert the reconciler invalidates the stale state and can self-heal on the next run. The diagnostic should also mention which manifest changed so users are not left debugging a silent cache condition.
Hive
Kura reconciler can’t self-heal a manifest-only regression (silent manifest change deadlock)
GitHub issue · Open
Summary
The Kura server reconciler (Tuist.Kura.Reconciler) keys convergence on only two things — the observed image tag and the @manifest_revision constant. A change to the rendered manifest that bumps neither cannot self-heal an already-provisioned instance. On 2026-07-01 this wedged kura-tuist-eu-central-1 for ~6 hours (16:00→22:00Z) with no public endpoint, and it could only be recovered by forcing a Kura runtime image-tag change.
What happened (incident)
- #11605 (
5e77e0ec60, “use the shared regional gateway for bare-metal Kura regions”) changedgateway_assignment/2so bare-metal (host-network) regions use the sharedkura-eu-centralingress class instead of a per-accountkgw-<acct>-eu-centralgateway that can never bind:80/:443on the single dedibox node. - It changed the rendered manifest (the instance’s
ingressClassName, and it stopped emitting a dedicatedKuraGateway) but did not bump@manifest_revision— a silent manifest change. - The already-provisioned
kura-tuist-eu-central-1stayed on the dedicated (unschedulable) gateway class. Its ingresses had no running controller → no address → external-dns published nothing →tuist-eu-central-1.kura.tuist.devwas NXDOMAIN → activation looped ([Kura.Reconciler] waiting on DNS ... :nxdomain) for ~6h. - Deploying the server binary with the #11605 fix did nothing — both convergence keys (image
0.10.15, revision2026-06-19-...) were unchanged, so the reconciler considered the instance fully converged. - What finally healed it: a Kura runtime image-tag change (
0.10.15→sha-5e77e0ec60c9, same content, new tag). That gave the image-keyed rollout path a mismatch →apply_deployment → Provisioner.rolloutre-rendered under #11605 → shared class, ingresses bound, DNS published,/up200,:active.
Root cause: the reconciler can’t heal a manifest-only regression
- Rollout fast path (
reconcile_deployment) is image-keyed only: ifobservedImage == deployment.image_tagit goes toactivate_and_mark_succeeded(probe only) and never re-renders. - Manifest-revision drift check lives in the projection pass (
project_server → reconcile_manifest_revision → apply_current_manifest), but that pass is skipped whenever the server has an open:pending/:runningdeployment. - A server wedged in the activation loop (stuck
:runningdeployment, endpoint never/up) is therefore blocked from both re-apply paths. Neither a plain server redeploy nor a@manifest_revisionbump reaches it — only an image-tag change (or manual surgery) breaks out.
Proposed fixes
- [preferred] Derive
@manifest_revisionfrom a content hash of the rendered manifest inputs (instead of a hand-maintained string), so any rendered-output change automatically shifts the revision and the projection drift path re-applies. This is the primary fix — it makes silent manifest regressions impossible. At minimum, add a CI guard that fails when the renderer changes but the constant doesn’t. - Teach
reconcile_deploymentto re-apply on manifest-revision drift, not just image drift, so a wedged (:running) instance can heal without an image change. - GC unassigned gateways inside
Provisioner.rollout— a dedicated→shared transition currently leaves the oldKuraGatewayCR behind (Pending, with perpetually-unschedulable controller pods); onlydestroydeletes gateways today. /ops“force re-apply” button — insert akura_deploymentsrow with a newforceflag honored byreconcile_deployment, giving a one-click manual override for a wedged instance (with audit trail via the existing deployment history). Note the button must bypass the image-equality gate; a plain same-image insert re-enters the deadlock.
Immediate recovery runbook (for the next time an instance is wedged)
- Force an image-tag change (Kura runtime bump), or
- Surgical patch: set
spec.ingressClassNameon theKuraInstance+ its ingresses to the correct shared class andkubectl delete kuragateway <stuck>, or kubectl delete kurainstance <name>to force the:not_found → apply_deploymentre-render path.
Cleanup still pending from this incident
Orphaned kgw-799b0dea3467-eu-central KuraGateway (Pending, + 2 unschedulable controller pods) — kubectl -n kura delete kuragateway kgw-799b0dea3467-eu-central.
Investigate, reproduce, or fix this item in an isolated repository. Each Flight preserves its outcome and agent session.
Start a Flight and preserve its objective, outcome, and session here.