Free O'Reilly E-Book: Learn GitOps & Kubernetes best practices with Argo CD: Up and Running. Download Now →

Free O'Reilly E-Book: Learn GitOps & Kubernetes best practices with Argo CD: Up and Running. Download Now →

Free O'Reilly E-Book: Learn GitOps & Kubernetes best practices with Argo CD: Up and Running. Download Now →

Fixing Kargo Stage Flip-Flopping Caused by Shared Argo CD Branches

Kent Rancourt

Fixing Kargo Stage Flip-Flopping Caused by Shared Argo CD Branches
Fixing Kargo Stage Flip-Flopping Caused by Shared Argo CD Branches

Two Kargo Stages, the same Freight promoted to each in turn. Every promotion turns one Stage green and the other red, and the next promotion swaps them back. Meanwhile, Argo CD shows both Applications synced and healthy, so nothing looks broken.

This is for platform engineers running Kargo on top of Argo CD who have hit that alternating-health pattern and can’t find the fault. The short version: Kargo is reporting a contradiction in your configuration, not a bug in either tool. Here’s how that contradiction gets built, and four ways to take it apart.

The Problem, in Short

Two Argo CD Applications can share a branch just fine. They have no opinion about what they're synced to: whatever commit sits at the head, that's what they sync to, no questions asked. The trouble starts when something else expects each of them to be synced to a different commit at the same time.

If your Stages go unhealthy one after another dev goes red, you promote, dev goes green and qa goes red, you promote again you have not found a bug. You have found two Applications sharing a branch, each with its own health check expecting a different commit at the same time.

This is a common problem and it has a one-line fix: leave desiredRevision unset unless you need the health check it enables.

The Setup: Two Argo CD Applications, One Branch

To keep things simple, say you have one "little a" application, and two instances of it: one you call "dev" and one you call "qa." You have two "Big A" Argo CD Applications also named "dev" and "qa" that manage those instances. They both sync to the head of the same branch, probably main, and they consume configuration from different paths within it.

The head of main floats. It always points at whatever was committed last. Both Applications follow it, both stay in sync, and both report healthy.

And they're happy for a specific reason that's easy to miss: nothing in this picture has an opinion about what each Application should be synced to right now. There's nothing to disagree with. "Whatever's newest" is not a position you can be wrong about.


Figure 1: Both Argo CD Applications are auto-synced to abcdef1, the current head of main. Kargo's dev health check still expects 1234567, so dev reports unhealthy while qa does not.

Figure 1: Both Argo CD Applications are auto-synced to abcdef1, the current head of main. Kargo's dev health check still expects 1234567, so dev reports unhealthy while qa does not.

How Kargo's desiredRevision Health Check Works

Now you add two Stages, dev and qa, whose promotion processes manage the desired configuration for those two instances — the very configuration those Applications sync to. That means Kargo now tracks what each Application should be synced to.

New Freight appears, referencing your latest image build. It gets promoted to dev, probably automatically. The promotion commits a change to your dev-specific configuration saying "point at that new image." Call that commit 1234567.

Your promotion process also runs an argocd-update step, and (using an expression) you've set desiredRevision to 1234567. That does something you may not have thought much about: it registers a health check that runs continuously from then on, and that check expects the dev Application to be observably synced to 1234567.

That health check is useful: you'd like to know if dev were synced to the wrong thing. So the check runs, and runs, and every time it finds dev synced to 1234567, exactly as promised.

Dev looks great, so you promote the same Freight to qa. That promotion commits a change to your qa-specific configuration. Call it abcdef1. Same argocd-update step, same pattern, desiredRevision: abcdef1. Now the qa health check expects qa to be synced to abcdef1, and finds it there every time.

Two Stages, two expectations, both satisfied -- for now.

Why Kargo Stages Start Flip-Flopping

abcdef1 is now the head of main. And dev has auto-sync turned on.

So dev syncs to abcdef1. Nothing broke -- auto-sync did precisely what you told it to do. But the next time the dev Stage runs its health check, it finds dev synced to abcdef1 when it was told to expect 1234567, and it reports what it sees: dev is not synced to what Kargo thinks it should be.

Your next promotion to dev fixes dev - and breaks qa. The next promotion to qa fixes qa -- and breaks dev. They will keep flip-flopping with every promotion, and no amount of inspecting Argo CD will reveal anything wrong with either Application, because there isn't anything wrong with either Application.

Here's what actually happened: you asked more than one environment to share a single floating pointer, and then asked Kargo to judge each of them against a different value of it. Kargo is not confused. Kargo is reporting a contradiction you configured.


Figure 2. Only one Application can match the head of main at a time, so every promotion flips which Stage reports healthy.

Figure 2. Only one Application can match the head of main at a time, so every promotion flips which Stage reports healthy.

Four Ways to Stop Kargo Stages From Flip-Flopping

Each option addresses the contradiction differently, and each comes with a trade-off. These four approaches come from Kargo's own documentation and the guidance its maintainers have given in response to real-world reports of this issue.

1. Turn off auto-sync. From then on it's Kargo's job to tell Applications when to sync, and nothing drags dev forward behind Kargo's back. The cost is real, though: you give up the self-healing that made auto-sync worth having.

2. Stop setting desiredRevision. The health checks drop their expectation of any particular revision, the contradiction evaporates, and everything goes green. This is the cheapest fix and, for most people, the right one. The cost is that you also give up ever being told when an Application isn't synced to what Kargo thinks it should be. You traded a false alarm for a blind spot. Sometimes that's a good trade. Know that you're making it.

3. Set updateTargetRevision: true. Now argocd-update doesn't just ask the Application to sync -- it rewrites the Application's targetRevision to match desiredRevision. Your Applications stop tracking the floating head of main and start saying "I should be synced to this specific commit." The contradiction disappears because each Application now has its own pointer instead of all sharing one.

4. Use stage-specific branches. This is the one we actually recommend -- and the one people flinch at first. Each Application points at the head of its own stage-specific branch, so nothing is shared and nothing is left to contradict. Auto-sync stays on and desiredRevision keeps working. The cost is a one-time change to each promotion process: a rendering step that writes plain YAML to the branch. This is the option Kargo's docs recommend, and the one the next two sections make the case for.

Option

What it does

Trade-off

Effort

Turn off auto-sync

Kargo controls exactly when Applications sync

You lose self-healing

Low

Leave desiredRevisionunset

Health checks stop expecting a specific revision

You lose visibility into drift

Low

Set updateTargetRevision: true

Each Application gets its own targetRevisioninstead of tracking main

Kargo rewrites the Application on every promotion

Medium

Stage-specific branches

Each Application points at its own branch -- no shared pointer at all

Requires a config-management step (helm-template/kustomize-build) per branch

Medium, one-time setup

Why Stage-Specific Branches Are the Best Fix

Stage-specific branches are the fix we recommend. The phrase "manage a dozen branches" scares people off, but it shouldn't. Here's the case.

Every Application points at the head of its own stage-specific branch. Auto-sync stays on, so you keep self-healing. Kargo keeps its opinion about what each Application must be synced to in order to count as healthy. Each Application has its own pointer, so there's nothing left to contradict.

And to be clear about what this costs you: you keep main. Your promotion processes keep doing to it whatever they do today. You just add a step or two at the end -- a helm-template or a kustomize-build -- and write the plain YAML that falls out to the root of the corresponding branch.

You don't manage those branches. They're storage. They could just as easily be environment-specific S3 buckets that happen to contain configuration, and nothing about your day would change. Nobody opens a PR against them. Nobody merges them into each other. They exist so that a pointer has somewhere to point.

Here's what you get in exchange: what's in the branch is the desired state, no guessing what helm template would produce to find out. And each branch's commit history becomes a genuinely useful audit trail of everything that environment has ever been asked to be, for free, without anyone building it.

Verdict: Stage-Specific Branches Win

The option to implement stage-specific branches may make readers pause because of a widely read GitOps blog post that argued against branch-per-environment. That post's argument got generalized into a broader case against stage-specific branches, even though the original point was narrower. Kargo's own docs recommend the practice for good reason: stage-specific branches work.

Turning off auto-sync, no longer setting desiredRevision, and setting updateTargetRevision: true are all real fixes, but each one trades something away to keep the peace: you give up self-healing, or the ability to catch real drift, or you have Kargo rewrite your Application's targetRevision on every single promotion. None of them touch the underlying cause -- multiple environments still sharing one branch. Stage-specific branches remove the shared pointer itself, which is why it's the fix that actually resolves the problem, not just the symptom.

Kargo Enterprise: Promotion at Production Scale

Kargo Enterprise extends the promotion model with production-scale features: a single policy-driven promotion layer across Kubernetes, Terraform, VMs, and serverless targets, plus automated rollbacks that recover a Stage without anyone needing to notice the failure, decide, and trigger the rollback by hand. 

Start a free trial →  Run Kargo Enterprise against your own pipeline.

Book a demo →  Bring your promotion setup and we’ll walk through it with you.

Keep Learning About Kargo

Ready to simplify delivery with Akuity?

Deploy, promote, and operate applications reliably, powered by OSS you trust and Intelligence you control.

Ready to simplify delivery with Akuity?

Deploy, promote, and operate applications reliably, powered by OSS you trust and Intelligence you control.

Ready to simplify delivery with Akuity?

Deploy, promote, and operate applications reliably, powered by OSS you trust and Intelligence you control.

Sign Up for Akuity Updates

Practical guidance on MTTR reduction, GitOps at scale, and safe automation, with product updates from the Argo CD and Kargo team.

@2026 Akuity Inc. All rights reserved.

Akuity Inc. 440 N. Wolfe Road, Sunnyvale, CA 94085-3869 US +1-510-771-7837

SOC2 Type 2 Compliant

Sign Up for Akuity Updates

Practical guidance on MTTR reduction, GitOps at scale, and safe automation, with product updates from the Argo CD and Kargo team.

@2026 Akuity Inc. All rights reserved.

Akuity Inc. 440 N. Wolfe Road, Sunnyvale, CA 94085-3869 US +1-510-771-7837

SOC2 Type 2 Compliant

Sign Up for Akuity Updates

Practical guidance on MTTR reduction, GitOps at scale, and safe automation, with product updates from the Argo CD and Kargo team.

@2026 Akuity Inc. All rights reserved.

Akuity Inc. 440 N. Wolfe Road, Sunnyvale, CA 94085-3869 US +1-510-771-7837

SOC2 Type 2 Compliant