Skip to content

A deploy passes its Flux step when Flux could not delete a removed resource #4734

Closed

Description

@devantler

🤖 Generated by the Agentic Engineer

Evidence

In merge-queue run 38113692499 (2026-10-11), Flux recorded these events on the apps layer:

Time Event
05:23:20Z PruneFailed: the delete of one HTTPRoute was refused by an admission policy (#4732)
05:25:25Z ReconciliationSucceeded

The deploy's 🔁 Trigger Flux reconciliation step printed ✔ apps reconciled, and the wait for the published revision passed. The run failed only because a later, unrelated check (the gateway route check) happened to notice the leftover route. A leftover object of any other kind would have gone through as a green deploy.

Who is affected

Anyone removing a resource from the platform. The change merges and deploys green while the resource keeps running, unmanaged, and nothing reports it. For prune-protected kinds the orphan alert covers this; for every other kind nothing does.

Expected behaviour

A deploy in which Flux failed to delete a resource it tried to prune does not pass. The failure names the resource and the reason Flux gave.

Acceptance criteria

  • After reconciliation, the deploy fails when any Flux layer reported a prune failure for the revision it just applied.
  • Resources that are prune-protected on purpose do not trip it (Flux skips those; it does not fail on them).
  • A test shows a failed delete fails the step and a clean prune passes it.

Notes

Flux retries the prune on the next reconcile, so one option is to read each layer's events or status for the applied revision rather than to change Flux settings. Which signal is reliable needs checking first.

Size: S–M.

Refinement (2026-10-17)

Where the gap is. The deploy's 🔁 Trigger Flux reconciliation step (.github/actions/deploy-prod/action.yml) runs scripts/reconcile-flux-workloads.sh, which runs ksail workload reconcile; KSail decides "reconciled" inside its own binary. The next step, scripts/wait-for-platform-flux-revision.sh, waits only for infrastructure-controllers to report the published revision and Ready. Nothing in the repository reads a Kustomization's events or a prune result: PruneFailed appears nowhere. Flux records a failed prune with the reason PruneFailed (fluxcd/pkg/apis/meta, PruneFailedReason).

Proposed change (one PR): a new bash check, run in deploy-prod straight after the exact-revision wait, that reads the Kubernetes events for the platform's Flux Kustomizations (bootstrap, infrastructure-controllers, infrastructure, apps, infrastructure-overprovisioning) since the reconcile step started, and fails when any carries reason PruneFailed. The failure prints the Kustomization and the kind, namespace and name of the object Flux could not delete, from the event, and nothing else from the message. Tenant Flux layers are out of scope for this slice; file them as a follow-up if the check proves reliable.

Decided here so the implementer does not re-open it: read events rather than change Flux settings; never retry or force the prune from the deploy; a failed or empty event read is a failure of the check, not a pass, so the check also proves it can see events (for example the ReconciliationSucceeded event the same reconcile wrote).

Acceptance criteria (in addition to the list above):

  • A self-test under scripts/tests/ with a fake kubectl covers: a PruneFailed event in the window fails and names the object; one from before the window does not; a clean reconcile passes; an unreadable or empty event list fails.
  • The test runs in CI's validate job, with the k8s path filter listing the script and the test.
  • The first merge-group deploys after merge pass the new step, read from their logs.

Priority Medium, Effort Medium, Service class Risk reduction. It closes the one case under #4979 where a broken deploy is judged healthy: a removed resource keeps running unmanaged with no signal. Effort includes a new deploy step, its test, CI wiring and two production deploys to verify. No prerequisites. Part of #4979.

Activity

  1. added theissue type on Oct 11, 2026
  2. moved this to 📥 Backlog in 🌊 Project Boardon Oct 11, 2026
  3. moved this from 📥 Backlog to 🫴 Ready in 🌊 Project Boardon Oct 17, 2026
  4. updated Effort Medium and Priority Medium on Oct 17, 2026
  5. devantler commented on Oct 17, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer (interactive Claude Code session)

    Refinement note (2026-10-17): what the production events look like

    A read-only look at the production cluster's flux-system events for Kustomization objects, taken today, settles two open points in the plan above.

    1. Each Flux event names the revision it belongs to. Every event from kustomize-controller carries the annotation kustomize.toolkit.fluxcd.io/revision with the value latest@sha256:<digest>. The check can therefore select events by the exact digest the deploy just published (PLATFORM_MANIFEST_DIGEST, already passed to the revision wait), instead of by a time window. That matches the first acceptance criterion word for word ("for the revision it just applied"), and an event from an earlier deploy can never trip it.
    2. The platform layers emit events only when they apply a new revision. In the hour of events read, bootstrap, infrastructure-controllers, infrastructure and apps each recorded ReconciliationSucceeded once, at the last deploy, and nothing for the periodic reconciles after it; only the root flux-system recorded one every ten minutes. So the visibility proof should be "at least one event from a platform layer carrying this revision", which every deploy produces because each publishes a new digest. A proof based on a fresh event in a time window would fail on a quiet cluster for no reason.

    Object identities in these messages use the Kind/namespace/name <action> form (for example … configured), so a PruneFailed message is expected to start the same way. If a message does not match that form, the check should still fail and say the object could not be named, rather than print the message.

    Cluster events expire after about an hour, which is far longer than the gap between the reconcile step and this check.

    One of the 160 merge-group failures counted in #4993 was this case (#4639), and it failed only because the gateway route check happened to notice the leftover route. A leftover object of another kind would have deployed green, so the count understates the risk.

  6. self-assigned this
    on Oct 17, 2026
  7. added a commit that references this issue on Oct 17, 2026
  8. moved this from 🫴 Ready to 👀 In Review in 🌊 Project Boardon Oct 17, 2026
  9. moved this from 👀 In Review to 🚀 Ready to Merge in 🌊 Project Boardon Oct 17, 2026
  10. moved this from 🚀 Ready to Merge to ✅ Done in 🌊 Project Boardon Oct 17, 2026
  11. devantler commented on Oct 17, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Verified in production: first deploy after #5021

    Merge-group run 39055989194 deployed #5021. The new step ran straight after the exact-revision wait, at 18:25:42Z:

    No Flux layer reported a failed prune for latest@sha256:296743a7… (10 event(s) checked).

    The deploy completed successfully, and the PR merged from the queue.

    • ✅ Acceptance 1: the deploy fails on a prune failure at its own revision. The self-test in CI's validate job covers this, and it ran green on the PR.
    • ✅ Acceptance 2: prune-protected objects do not trip the check. Flux skips them without raising PruneFailed, and this deploy, with the protected Backstage leftovers still in the cluster, read clean.
    • ✅ Self-test with a fake kubectl covering the four named cases, wired into validate with the k8s path filter.
    • 🟡 "The first merge-group deploys pass the new step": one deploy has passed so far. The next platform deploy's log is the second read.
  12. moved this from ✅ Done to 📊 Verifying in 🌊 Project Boardon Oct 17, 2026
  13. moved this from 📊 Verifying to ✅ Done in 🌊 Project Boardon Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Fields

Priority

Medium

Effort

Medium

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions