Repository navigation
feat(gitops): observe prints the refusals a cell records against a delivered workload - #1294
Merged
antoinetoussaint-byte merged 1 commit intoNov 4, 2026
Conversation
antoinetoussaint-byte
force-pushed
the
feat/observe-rollout-admission-refusals
branch
from
November 4, 2026 04:50
4cbac8b to
4c585ae
Compare
…livered workload After a promotion merges and Argo CD syncs, a pod the cell's ValidatingAdmissionPolicy refuses is never created. Argo CD reports the application Progressing or Degraded and says nothing of why; the why is a FailedCreate event on the workload's controller (the Deployment's ReplicaSet, the StatefulSet, the DaemonSet, the Job), visible only in `kubectl describe rs`. `codefly deploy gitops observe` waited its whole budget and ended with "health observation on Argo CD timed out" — or, when the budget ran out during an `argocd app get`, with "signal: killed". `observe` now reads, while an application is still Progressing or Degraded, the events of the delivered workloads' controllers through the cluster the environment declares (its kubeconfig and context, resolved the way every other cluster call of the environment resolves them), and prints every FailedCreate verbatim, once, as it is first seen, naming the workload. An admission denial ends the observation at once, non-zero: no retry of the controller creates that pod. Any other FailedCreate is printed and the wait goes on; the timeout then names the workloads it was still waiting on, with Argo CD's health message, and every refusal seen. A cluster that cannot be read is reported once and the Argo CD health wait continues as before. The budget running out mid-call is now reported as the timeout and what it waited on, not as the killed call. The polling loop moves to awaitApplications, unchanged in what it awaits. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
antoinetoussaint-byte
force-pushed
the
feat/observe-rollout-admission-refusals
branch
from
November 4, 2026 04:57
4c585ae to
45f935b
Compare
antoinetoussaint-byte
enabled auto-merge
November 4, 2026 04:58
This was referenced Nov 4, 2026
Closed
Closed
Closed
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
After the promotion PR merges and Argo CD syncs the Blue cell, a pod refused by the cell's ValidatingAdmissionPolicy is visible only in
kubectl describe rsevents (no approved execution matches this pod…,a container of this pod declares an exec lifecycle hook or an exec probe…). Argo CD reports the application Degraded/Progressing with no reason, andcodefly deploy gitops observewaited its whole budget to sayhealth observation on Argo CD timed out: …— orsignal: killedwhen the budget ran out during anargocd app get.Seam
codefly deploy gitops observe, not a newpublish --waitordoctor cell --live:observeis already the verb run after a publish, already polls the publication's Argo CD applications until Synced + Healthy (which is each delivered workload rolled out), already has the bounded wait (--timeout, default 10 min) and already reads the application'sstatus.resources— the list of workloads the publication delivered. The gap is only that, while an application is not Healthy, nothing reads why.deploy gitops publishends when the promotion pull request is open; the rollout happens after a review and a merge it does not wait for, so a--waitthere would be a second observe.doctorjudges declarations, not a live cluster (feat(doctor): judge what a cell would enforce, not just a solution's shape #1273 judges what a cell would enforce).kubectlthrough exec.pkg/control.ObserveGitOpsalready selects the environment, so it resolves the cluster there withdeployments.GetK8sConfigandenv.Cluster.Context— the same resolution every other cluster call of the environment uses — and hands them to the observation.Change
pkg/gitops/observe_workloads.go: aworkloadObserverthat, for the applications a sweep found pending, reads each namespace's events (and, for a Deployment, its ReplicaSets, to attribute an event on one to the Deployment that owns it) through kubectl, keeps a FailedCreate — or a Warning that is an admission denial — recorded against a delivered workload since its application's sync began (operationState.startedAt; an older event is a previous rollout's, a ReplicaSet kept in the Deployment's history or a Job re-synced under the same name; an event whose time cannot be read is kept rather than hidden), and prints each distinct refusal once, verbatim, naming the workload (payments/Deployment/api FailedCreate: Error creating: pods "api-…" is forbidden: ValidatingAdmissionPolicy 'cell-admission' … denied request: no approved execution matches this pod).pkg/gitops/observe.go: the polling loop moves toawaitApplications(unchanged in what it awaits; it also broughtObserveunder the gocyclo ceiling). After a sweep that is not all Healthy it inspects the pending applications; an admission denial — the denial's own wording,denied request, not theis forbiddenevery 403 carries — ends the observation non-zero at once, because no retry creates that pod. Any other FailedCreate (a quota exceeded, a service account the admission cache has not seen yet: forbidden too, but retried past) is printed and the wait continues. The timeout message now adds what it was still waiting on (Argo CD's per-resource health and message), every refusal seen, and whether the cluster could not be read; the budget running out during anargocdcall is reported as that timeout rather thansignal: killed.ObserveRequestgainsKubeconfig,KubeContextandOutput; the command passes stdout;pkg/control.ObserveGitOpsfills the cluster from the environment when the caller did not.docs/commands.md: whatobserveprints and when it stops.Classification: fix, at the place that owns post-publish observation. Reused:
argocd app get'sstatus.resourcesfor the delivered workloads, the existing--timeoutbudget and poll loop, thecommandexec helper and its PATH-fake test seam,deployments.GetK8sConfigand the environment's declaredcluster.context. Deliberately not reused:pkg/deployments.completionObserver— it observes adeploythe CLI applied itself against a verified k3d target and reads workload status, not controller events; what is missing here is the events, and importing it intopkg/gitopswould bring the verified-target machinery along for nothing. No--waitwas added topublish.Review
Independent fresh-context review at 4cbac8b found two blockers, both fixed in this head with a test each:
forbiddenmade retried refusals (quota, service-account cache lag) terminal, and events of a previous rollout (an old ReplicaSet in the Deployment's history) were attributed to the current one.Tests
pkg/gitops/observe_workloads_test.go, with a fakeargocdanswering a sequence of application states and a fakekubectlon PATH (the repo's "fake clientset") answeringget events/get replicasetsand logging its arguments:--kubeconfigand--context;is forbidden: exceeded quota:wording) is printed exactly once across three pending sweeps and the observation still succeeds once Argo CD turns Healthy;These do not compile on
main(Output,Kubeconfig,KubeContextdid not exist) and the timeout/denial assertions fail against the previous loop by construction.go test ./pkg/gitops/... ./pkg/control/... ./cmd/...,golangci-lint runon the touched packages (0 issues) andTestCLIReferenceIsCurrent(no flag changed) pass.Not verified
No live cluster: the exact shape of the Blue cell's FailedCreate messages is taken from the observation in the task (
kubectl describe rs) and from Kubernetes'pods "x" is forbidden: ValidatingAdmissionPolicy … denied request:wrapping; a denial that is worded withoutdeniedwould be printed but not end the wait early. A realcodefly deploy gitops observe … --env blueagainst the cell was not run.Would file (not opened)
health.messageis also where aProgressingDeployment says0 of 1 updated replicas are available; a follow-up could read the Deployment's conditions (ProgressDeadlineExceeded) to end the wait earlier on a rollout that will not finish for reasons other than admission.🤖 Generated with Claude Code