{"id":"IL-0058","org":"Argo CD users","date":"2026-07-08","docs":["https://github.com/argoproj/argo-cd/issues/6915","https://github.com/argoproj/argo-cd/issues/29739"],"title":{"en":"Argo CD deleted namespaces shared by several applications, leaving about 97 applications stuck Missing and OutOfSync","ko":"Argo CD 에서 여러 앱이 함께 선언한 네임스페이스가 force 동기화로 지워져 앱 약 97개가 Missing/OutOfSync 상태에서 빠져나오지 못했다."},"domain":["kubernetes","gcp"],"impact":{"scope":"shared namespaces deleted; about 97 applications stuck Missing/OutOfSync","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"The sync option Prune=false only protects the prune path; it does not stop a force sync from deleting and recreating a resource. When two Argo CD applications declare the same namespace or other cluster-scoped resource, force-syncing the application that does not own the tracking label can delete it at any time.","ko":"argocd.argoproj.io/sync-options: Prune=false 는 prune 경로만 막고, force replace 로 일어나는 삭제는 막지 않는다. 수동 동기화는 자동 동기화와 달리 OutOfSync 검사를 거치지 않으므로 언제든 실행된다. 여러 앱이 같은 클러스터 범위 리소스를 선언하면 tracking-ID 충돌이 생기고, 소유하지 않은 앱을 force 동기화하면 그 리소스가 지워졌다가 다시 만들어질 수 있다. 처음 세운 재시작과 캐시 가설은 코드를 읽어 보니 맞지 않았다."},"related":[],"sources":[{"url":"https://github.com/argoproj/argo-cd/issues/28630","kind":"github","license":"unknown","archived":null}],"summary":{"en":"A user running Argo CD v3.3.9 (Helm chart argo-cd-9.5.11) on GKE 1.30.x, with the application-controller as a sharded Deployment with several replicas, reported that shared namespaces were deleted after a rolling restart of the controller. The cluster had a large number of resources (the reproduction mentions 20k+ ReplicaSets), and several applications declared the same namespace.\n\nThe report first blamed timing: the controller seemed to process syncs before its cluster cache had finished the initial LIST, so it misjudged who owned the shared namespace and either pruned it through automated sync or force-replaced it through a manual sync. The namespaces entered Terminating and about 97 applications were stuck Missing/OutOfSync.\n\nA commenter who read the controller and gitops-engine code found the automated prune path guarded and the cache locked while it repopulates. The real gap was the manual sync with force: true: when two applications declare the same resource and the one that does not own the app.kubernetes.io/instance tracking label is force-synced, Argo CD replaces the resource with kubectl replace --force, deleting and recreating it, regardless of that application's Prune=false setting. That can happen any time, not only after a restart. The reporter agreed, closed this issue and refiled the problem as #29739.","ko":"이 사례는 Argo CD v3.3.9 를 GKE 1.30.x 에서 쓰는 사용자가 2026-07-08 에 올린 GitHub 이슈다. application-controller 는 샤딩을 켠 Deployment 로 배포돼 복제본 여러 개가 돌고 있었다. application-controller 를 롤링 재시작한 뒤 여러 앱이 함께 쓰는 네임스페이스가 지워져 Terminating 상태가 됐고, 앱 약 97개가 Missing/OutOfSync 에서 빠져나오지 못했다.\n\n보고자는 처음에 원인을 재시작 직후의 캐시 문제로 봤다. 컨트롤러가 클러스터 캐시의 EnsureSynced() 초기 LIST 를 끝내기 전에 동기화를 처리했고, 불완전한 캐시 때문에 공유 네임스페이스가 어느 앱의 것인지 잘못 판단했다는 설명이었다. 삭제 경로로는 두 가지를 들었다. 하나는 prune: true 인 자동 동기화가 네임스페이스를 고아로 보고 지운 경로다. 다른 하나는 UI 에서 force: true 로 일괄 수동 동기화를 하면서 tracking-ID 충돌이 난 네임스페이스에 kubectl replace --force 를 실행한 경로다.\n\n다른 사용자 ivomarino 가 컨트롤러와 gitops-engine 코드를 읽고 다른 해석을 내놓았다. autoSync 는 앱이 이미 OutOfSync 일 때만 동작하고, 라이브 상태를 읽지 못하면 CompareAppState 가 앱을 OutOfSync 가 아니라 Unknown 으로 둔다. EnsureSynced() 는 캐시를 비우고 다시 채우는 동안 내내 c.lock 을 잡고 있어서, 읽는 쪽이 반쯤 채워진 캐시를 볼 수 없다. 따라서 첫 번째 경로는 막혀 있다. 수동 동기화에는 OutOfSync 검사가 없으므로, force 동기화와 tracking-ID 충돌이 겹치면 Argo CD 는 대상 앱의 Prune=false 와 상관없이 리소스를 강제로 교체한다. 그는 이 문제가 재시작과 상관없이 언제든 재현될 수 있다고 봤고, 직접 재현해 보지는 않았다고 밝혔다.\n\n보고자는 이 분석이 맞다고 인정했다. 두 앱이 같은 리소스를 선언하고 그 리소스를 소유하지 않은 쪽을 force 동기화하면 언제든 재현된다는 결론이었다. 보고자는 이 이슈를 닫고 원인을 정확히 적은 #29739 를 새로 열었다."},"trigger":{"en":"A manual bulk sync with force: true from the Argo CD UI, run during the window after an application-controller restart, on an application whose manifests declared a namespace whose app.kubernetes.io/instance label pointed to a different application.","ko":"여러 앱이 같은 네임스페이스를 매니페스트에 선언했고, 네임스페이스의 app.kubernetes.io/instance 레이블은 그중 다른 앱을 가리켰다. application-controller 를 롤링 재시작한 직후, UI 에서 레이블이 가리키지 않는 앱을 force: true 로 수동 동기화했다. Argo CD 가 kubectl replace --force 로 네임스페이스를 지우고 다시 만들었다."},"severity":"unknown","symptoms":["resources-deleted"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":null,"components":["argocd"],"root_cause":"software-bug","remediation":[],"action_items":[{"text":{"en":"Refile the problem as #29739: a manual force sync hard-replaces shared cluster-scoped resources through a tracking-ID conflict, ignoring Prune=false","ko":"보고자가 이 이슈를 닫고, 재시작과 상관없는 재현 절차와 함께 force 동기화와 tracking-ID 충돌 문제를 다루는 #29739 를 새로 열었다."}}],"contributing":["no-safeguard-on-destructive-change","assumption-unverified","manual-action-worsened"],"recurrence_of":null,"symptoms_text":{"en":"After a rolling restart of the Argo CD application-controller, namespaces that several applications shared went into the Terminating state. About 97 applications were then stuck in a Missing/OutOfSync deadlock.\n\nThe controller logs carried two warnings. At 16:59:58Z: \"Detected changes to resource Namespace/foo which is currently being deleted\". At 17:00:10Z, about three minutes after the restart: \"Failed to list *unstructured.Unstructured using watch: very old resource version: ... continue parameter too old\" for type=ReplicaSet.apps. The reporter read the second message as proof that the controller's initial LIST of cluster resources was still running when the deletions happened, and noted that the \"Cluster cache informer synced\" log line appears when the watch starts, not when the initial LIST completes.\n\nThe reporter saw two deletion paths at the same time. Automated sync with prune: true resumed right after the restart and, the reporter believed, pruned a namespace it wrongly saw as orphaned. Separately, someone ran a manual bulk sync with force: true from the UI; the shared namespace's app.kubernetes.io/instance label pointed to another application, and Argo CD issued kubectl replace --force, which deleted the namespace and recreated it. The initiatedBy field in the controller logs confirmed the manual sync. The annotation argocd.argoproj.io/sync-options: Prune=false did not stop the force replace.\n\nA later reading of the controller code, which the reporter accepted, found the automated path guarded: auto-sync runs only when an application is already OutOfSync, a failed live-state load marks it Unknown, and the cluster cache holds a lock while it repopulates. The manual force sync is not behind that check, so the deletion can happen at any time, not only after a restart.","ko":"application-controller 를 롤링 재시작한 뒤, 여러 앱이 함께 쓰는 네임스페이스가 Terminating 상태가 됐다. 이 네임스페이스를 쓰는 앱 약 97개가 Missing/OutOfSync 상태에서 빠져나오지 못했다.\n\n컨트롤러 로그에는 msg=\"Detected changes to resource Namespace/foo which is currently being deleted\" 경고가 남았다. 재시작 약 3분 뒤에는 ReplicaSet.apps 목록 조회에서 \"Failed to list *unstructured.Unstructured using watch: very old resource version: ... continue parameter too old\" 경고가 나왔다.\n\n보고자는 처음에 클러스터 캐시의 초기 LIST 가 끝나기 전에 동기화가 돌았다고 봤고, continue parameter too old 경고를 그 증거로 들었다. 보고자는 삭제 경로로 두 가지를 의심했다. 하나는 prune: true 인 자동 동기화가 재시작 직후 다시 돌면서 네임스페이스를 고아로 보고 지운 경로다. 다른 하나는 UI 에서 force: true 로 일괄 수동 동기화를 하면서 tracking-ID 충돌이 난 네임스페이스에 kubectl replace --force 를 실행한 경로다.\n\nargocd.argoproj.io/sync-options: Prune=false 설정이 있었지만 두 번째 경로를 막지 못했다. 이 설정은 prune 경로만 보호하고 force replace 는 막지 않는다. 컨트롤러 로그의 initiatedBy 필드로 수동 동기화가 삭제를 일으켰다는 것을 확인했다."},"updated_at":"2026-11-10T00:00:38.956372Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0058","org_slug":"argo-cd-users"}