Skip to content

fix: reject stale copies of completed workflows using resourceVersion comparison#16357

Merged
Joibel merged 1 commit into
argoproj:mainfrom
Joibel:fix-16305-stale-completed-workflows
Jul 3, 2026
Merged

fix: reject stale copies of completed workflows using resourceVersion comparison#16357
Joibel merged 1 commit into
argoproj:mainfrom
Joibel:fix-16305-stale-completed-workflows

Conversation

@Joibel

@Joibel Joibel commented Jul 2, 2026

Copy link
Copy Markdown
Member

Fixes #16305

Motivation

The recently-completed workflow cache added in #12198 records informer keys
(namespace/name) but processNextItem checked it with name only. A
Kubernetes name can never contain /, so the check never matched and the
"Cache: Rejecting recently deleted" guard was dead code: a stale, still-running
copy of a recently completed or deleted workflow re-entering the informer
(e.g. via a relist after watch interruption) could be reprocessed,
potentially recreating pods.

Fixing just the key would introduce a new problem: a name-keyed membership
check would wrongly reject a retried workflow, or a recreated workflow
reusing a name, for up to 10 minutes after completion.

Since #15090 the controller also tracks staleness for in-flight workflows via
the workflows.argoproj.io/last-seen-version annotation plus an in-memory
UID→resourceVersion map, using equality comparison because resourceVersions
could not officially be ordered. k8s.io/apimachinery/pkg/util/resourceversion
now provides sanctioned ordered comparison of resourceVersions of the same
object, which lets both mechanisms be replaced by one simpler, UID-keyed one.

Modifications

  • Replace recentCompletions and lastSeenVersions with a single
    lastWrittenVersions map, keyed by workflow UID, recording the last
    resourceVersion this controller wrote (or saw complete/be deleted) and
    when the workflow completed.
  • isOutdated now compares resourceVersions with
    resourceversion.CompareResourceVersion and reports whether the recorded
    version was the workflow's completed/deleted state: stale copies of
    in-flight workflows are requeued (as before), stale copies of completed
    workflows are dropped. Incomparable resourceVersions fail open (the
    workflow is processed) with a warning.
  • Retry is unaffected because this is an ordering check, not a membership
    check: the recorded completion resourceVersion acts as a floor, so only
    copies strictly older than it are rejected. A retry mutates the same
    object (same UID) and therefore always carries a newer resourceVersion;
    the controller's first write of the retried workflow then clears the
    completed state, restoring requeue semantics for in-flight staleness.
    A recreated workflow reusing a name has a new UID and is untracked.
  • persistUpdates/persistWorkflowSizeLimitErr record the Update response
    resourceVersion; the controller no longer writes the
    workflows.argoproj.io/last-seen-version annotation, and the constant is
    removed. Existing annotations on workflows are ignored and harmless.
  • Records of completed workflows expire after 10 minutes (matching the old
    cache's retention); expiry scans are throttled to once per minute.
    A stale completion event cannot regress the recorded version.

Verification

  • New unit tests (TestIsOutdated, TestExpireCompletedVersions) cover the
    Recently completed workflow cache stores namespace/name but checks name #16305 regression scenario, the full retry cycle (newer copy passes while
    the completed record stands; the first controller write after retry clears
    the completed state so stale copies requeue rather than drop), name reuse,
    fail-open on incomparable resourceVersions, no-regress on stale completion
    events, and record expiry. Neither previous mechanism had unit coverage.
  • go test ./workflow/controller/... ./workflow/common/... passes.
  • golangci-lint run on the changed packages reports only pre-existing
    issues on untouched lines.

Documentation

Not needed: this is internal controller bookkeeping. The removed
last-seen-version annotation was never documented.

AI

The idea to swtich to resourceVersion was mine.
This PR was developed with Claude Code (Claude Fable 5): analysis of the issue, the code changes, tests, and this PR text, reviewed and directed by me.

… comparison. Fixes argoproj#16305

Replace the recently-completed workflow cache and the last-seen-version
annotation machinery with a single UID-keyed record of the last
resourceVersion written by the controller, compared numerically using
k8s.io/apimachinery/pkg/util/resourceversion.

The recently-completed cache recorded namespace/name keys but was
checked with name only, so it never matched and stale copies of
completed or deleted workflows could be reprocessed. Keying by UID also
avoids wrongly rejecting a retried workflow or a recreated workflow
that reuses a name. The controller no longer writes the
workflows.argoproj.io/last-seen-version annotation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Alan Clucas <alan@clucas.org>
@Joibel
Joibel requested a review from a team as a code owner July 2, 2026 10:46
@Joibel
Joibel marked this pull request as draft July 2, 2026 10:46
@Joibel
Joibel requested a review from eduardodbr July 2, 2026 10:46
@Joibel
Joibel marked this pull request as ready for review July 2, 2026 11:26
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 526a40ee-070c-4314-bfe2-da19a8592c6b

📥 Commits

Reviewing files that changed from the base of the PR and between d1f52ea and b8bd971.

📒 Files selected for processing (4)
  • workflow/common/common.go
  • workflow/controller/controller.go
  • workflow/controller/controller_test.go
  • workflow/controller/operator.go
💤 Files with no reviewable changes (1)
  • workflow/common/common.go
👮 Files not reviewed due to content moderation or server errors (3)
  • workflow/controller/controller.go
  • workflow/controller/controller_test.go
  • workflow/controller/operator.go

📝 Walkthrough

[!WARNING]

Walkthrough skipped

File diffs could not be summarized.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Joibel
Joibel requested a review from isubasinghe July 2, 2026 12:32
@Joibel Joibel added cherry-pick/3.7 Cherry-pick this to release-3.7 cherry-pick/4.0 Cherry-pick this to release-4.0 labels Jul 2, 2026
@Joibel
Joibel merged commit a7a7a8d into argoproj:main Jul 3, 2026
75 of 78 checks passed
@Joibel Joibel added cherry-pick/3.7 Cherry-pick this to release-3.7 cherry-pick/4.0 Cherry-pick this to release-4.0 and removed cherry-pick/3.7 Cherry-pick this to release-3.7 cherry-pick/4.0 Cherry-pick this to release-4.0 labels Jul 6, 2026
@argo-cd-cherry-pick-bot

Copy link
Copy Markdown

❌ Cherry-pick failed for 3.7. Please check the workflow logs for details.

@argo-cd-cherry-pick-bot

Copy link
Copy Markdown

❌ Cherry-pick failed for 4.0. Please check the workflow logs for details.

@Joibel

Joibel commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

Not cherry-picking to 3.7 because the k8s libs don't support comparing resource versions

@Joibel Joibel added the cherry-pick/3.7 Cherry-pick this to release-3.7 label Jul 6, 2026
@argo-cd-cherry-pick-bot

Copy link
Copy Markdown

❌ Cherry-pick failed for 3.7. Please check the workflow logs for details.

@Joibel

Joibel commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

Not cherry-picking to 3.7 because the k8s libs don't support comparing resource versions

Actually, going to try the same tactic for 3.7 as 4.0.

Joibel added a commit that referenced this pull request Jul 6, 2026
… comparison (cherry-pick #16357 for 4.0) (#16382)

Signed-off-by: Alan Clucas <alan@clucas.org>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cherry-pick/3.7 Cherry-pick this to release-3.7 cherry-pick/4.0 Cherry-pick this to release-4.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recently completed workflow cache stores namespace/name but checks name

3 participants