Skip to content

fix: process Delete event for app already deleted on the agent - #1101

Merged
jgwest merged 1 commit into
argoproj-labs:mainfrom
KR-Ravindra:fix/delete-event-for-already-deleted-app
Sep 24, 2026
Merged

jgwest merged 1 commit into
argoproj-labs:mainfrom
KR-Ravindra:fix/delete-event-for-already-deleted-app

Conversation

@KR-Ravindra

@KR-Ravindra KR-Ravindra commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do / why we need it:

Problem

When an Application is deleted on the principal and, at about the same time, by a user on the managed agent, the Delete event from the principal reaches the agent after the Application is already gone from the workload cluster. The agent fails to process that event, so it never learns that the deletion was legitimate. The concurrent local deletion is then treated as an unauthorized deletion and the Application is recreated; every further attempt to delete it is reverted the same way. The Application no longer exists on the principal, so nothing can ever remove it again until the agent is restarted.

Root cause

agent/inbound.go:875 (deleteApplication): the lookup of the existing Application via a.appManager.Get returns the NotFound error straight to the caller. Everything that tells the agent the deletion is expected runs only after that lookup succeeds: a.deletions.MarkExpected(...) and a.sourceCache.Application.Delete(...). As a result the source UID of the deleted Application is never marked as an expected deletion and the stale source cache entry is kept, and addAppDeletionToQueue in agent/outbound.go recreates the Application through RevertUserInitiatedDeletion.

The NotFound case is already handled when Delete itself reports NotFound a few lines further down; only the Get path was missing it.

Fix

The Delete event path now goes through a new deleteApplicationFromEvent. It calls deleteApplication and, if that returns NotFound from Get, marks the deletion as expected for the source UID carried by the incoming event (sourceUIDForApp(app), which is the principal side UID and therefore the value of the source-uid annotation on the agent side copy), drops the stale source cache entry in managed mode, unmanages the app, logs at debug level and returns nil. Errors other than NotFound are still returned unchanged.

deleteApplication itself keeps returning NotFound from Get. It is also called on a source UID mismatch, where the incoming app is the new application; marking its source UID as expected there would let a later user-initiated deletion of the recreated app go unreverted (pointed out in review). That path therefore behaves as on main. Per review, the app is now also unmanaged in both NotFound cases.

Which issue(s) this PR fixes:

Fixes #1056

How to test changes / Special notes to the reviewer:

How tested

Added the unit test Delete: App already deleted on the agent must not be recreated to Test_ProcessIncomingAppWithUIDMismatch in agent/inbound_test.go. It seeds the source cache with the incoming UID, makes the backend Get return NotFound, sends a Delete event through processIncomingApplication, and asserts that no error is returned, that no Delete/Create backend call is made, that the source cache entry is gone and that the deletion is marked as expected.

Added Create: Old app gone before delete on UID mismatch must not mark the new UID as expected to the same test: the identity check sees the old app, the second Get returns NotFound, and it asserts the error is surfaced, no Delete/Create is made and neither the new nor the old source UID is marked as an expected deletion.

Before the fix:

--- FAIL: Test_ProcessIncomingAppWithUIDMismatch (0.00s)
    --- FAIL: Test_ProcessIncomingAppWithUIDMismatch/Delete:_App_already_deleted_on_the_agent_must_not_be_recreated (0.00s)
        inbound_test.go:401:
            	Error:      	Received unexpected error:
            	            	application.argoproj.io "test" not found
FAIL	github.com/argoproj-labs/argocd-agent/agent	0.059s

After the fix:

--- PASS: Test_ProcessIncomingAppWithUIDMismatch (0.01s)
    --- PASS: Test_ProcessIncomingAppWithUIDMismatch/Delete:_App_already_deleted_on_the_agent_must_not_be_recreated (0.00s)
ok  	github.com/argoproj-labs/argocd-agent/agent	0.055s

go test ./agent/, go vet ./agent/ and go build ./... pass.

Links

Checklist

  • Documentation update is required by this PR (and has been updated) OR no documentation update is required.

This change was prepared with an AI agent operated by KR-Ravindra, who reviewed and tested it.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of application deletion events when the application has already been removed.
    • Prevented unnecessary delete or recreate operations in this scenario.
    • Removed stale application state and correctly recorded the deletion as expected.
    • Improved handling of create events when an existing application disappears during processing, ensuring the operation reports an error without incorrectly recording a deletion.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: e347ada4-cc82-41ff-b74d-a0031799891f

📥 Commits

Reviewing files that changed from the base of the PR and between 7bd60e0 and 0c9e69b.

📒 Files selected for processing (2)
  • agent/inbound.go
  • agent/inbound_test.go

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

The inbound delete path now handles applications already deleted on the agent. It records the deletion as expected, removes stale source-cache state, unmanages the application, and avoids recreation. Tests cover delete and UID-mismatch create races.

Changes

Application deletion race handling

Layer / File(s) Summary
Handle missing applications during deletion
agent/inbound.go, agent/inbound_test.go
Delete events use a wrapper that handles NotFound by recording the source UID, removing managed source-cache state, and unmanaging the application. Other event paths still receive NotFound. Tests verify cleanup, no recreation, and no incorrect expected-deletion markers.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 0c9e6

Delete events for already-removed applications now complete without recreation while stale state is cleared. The supplied tests and checks indicate the change is ready to merge.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy the coding requirements in issue #1056. deleteApplicationFromEvent captures the incoming event source UID and handles NotFound from the initial lookup as an expected deletion. …
Out of Scope Changes check ✅ Passed The changes remain within issue #1056. The production change handles the principal-side delete event race, and the tests cover the new delete behavior and the related create-event error path. No unrel…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: fixing Delete event handling when the Application was already deleted on the agent.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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.

@KR-Ravindra
KR-Ravindra marked this pull request as ready for review September 9, 2026 07:54
@KR-Ravindra

Copy link
Copy Markdown
Contributor Author

Round 1 self-review.

  1. Checked against agent/inbound.go on main: deleteApplication returns the Get error (L875-878) before MarkExpected and the source-cache delete run, while a NotFound from Delete (L886-891) is already tolerated. The new branch mirrors that existing path, keyed by the incoming event's source UID, which is the value the agent-side copy carries in its source-uid annotation.
  2. go test ./agent/ and go vet ./agent/ pass on the branch. With main's inbound.go swapped in, Test_ProcessIncomingAppWithUIDMismatch/Delete:_App_already_deleted_on_the_agent_must_not_be_recreated fails with application.argoproj.io "test" not found, matching the description.
  3. No other PR references Race condition when Application is deleted simultaneously on both principal/managed agent: creates undeletable Application on managed-agent #1056. DCO and title lint pass; the ci/CodeQL/image workflows are waiting for a maintainer to approve the run.

Ready for review.

@codecov-commenter

codecov-commenter commented Sep 9, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.94737% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.50%. Comparing base (a7894bc) to head (0c9e69b).
⚠️ Report is 18 commits behind head on main.

Files with missing lines Patch % Lines
agent/inbound.go 78.94% 3 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1101      +/-   ##
==========================================
+ Coverage   48.31%   48.50%   +0.18%     
==========================================
  Files         131      131              
  Lines       19659    19966     +307     
==========================================
+ Hits         9499     9684     +185     
- Misses       9307     9414     +107     
- Partials      853      868      +15     
Flag Coverage Δ
unit-tests 48.50% <78.94%> (+0.18%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@KR-Ravindra

Copy link
Copy Markdown
Contributor Author

@jgwest @jannfis gentle ping on this one when you have a moment: fix for #1056, deleteApplication returned on the Get NotFound before marking the deletion expected and dropping the source-cache entry, so an app deleted on the agent at the same time as on the principal was recreated from stale state; the new branch mirrors the existing NotFound-from-Delete path, with a unit test. CI is green.

Comment thread agent/inbound.go Outdated
@KR-Ravindra
KR-Ravindra force-pushed the fix/delete-event-for-already-deleted-app branch from 3932f74 to 7bd60e0 Compare September 19, 2026 05:03
Comment thread agent/inbound.go Outdated
// app can never be deleted again.
logCtx.Debug("application is not found, perhaps it is already deleted")
a.deletions.MarkExpected(sourceUIDForApp(app))
if a.mode == types.AgentModeManaged {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's one scenario where this can allow unauthorized deletion. The function deleteApplication is also invoked when there is a source UID mismatch between the incoming app and the existing app. The agent will then delete the existing app using deleteApplication and create the incoming app.

We are marking the deletion as expected using the source UID of the new app, thereby marking the deletion for the new app as legitimate instead of the old app. This allows the user to delete the app, and the agent will not recreate it because it was marked as expected.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chetan-rns you're right, thanks. In the mismatch path app is the incoming (new) application, so marking its source UID would have let a later user deletion of the recreated app go unreverted.

Done in 0c9e69b: deleteApplication returns NotFound from Get unchanged again, so the mismatch path behaves as on main (error, retry). The Delete event path now goes through deleteApplicationFromEvent, which handles NotFound by marking the event's source UID as expected, dropping the source cache entry and unmanaging. New test covers the mismatch case (Get NotFound after the identity check) and asserts the error is surfaced and the new UID is not marked expected. Could you take another look?

This comment was drafted with AI assistance and checked against the code.

When the principal sends a Delete event for an Application that no longer
exists on the managed agent (for example because a user deleted it on the
agent at the same time), deleteApplication returned the NotFound error from
Get before marking the deletion as expected or dropping the source cache
entry. The agent then treated the concurrent local deletion as unauthorized
and recreated the Application, and every further deletion attempt was
reverted as well, until the agent was restarted.

Handle NotFound from Get in the Delete event path only. The new
deleteApplicationFromEvent marks the deletion as expected for the source
UID carried by the event, removes the stale source cache entry and
unmanages the app. deleteApplication itself keeps returning NotFound: on a
source UID mismatch the incoming app is the new one, and marking its source
UID as expected would let a later user-initiated deletion of the recreated
app go unreverted. The app is also unmanaged when Delete reports NotFound.

Signed-off-by: KR Ravindra <42912207+KR-Ravindra@users.noreply.github.com>
@KR-Ravindra
KR-Ravindra force-pushed the fix/delete-event-for-already-deleted-app branch from 7bd60e0 to 0c9e69b Compare September 22, 2026 11:26
@jgwest

jgwest commented Sep 24, 2026

Copy link
Copy Markdown
Member

Thanks @KR-Ravindra and thanks @chetan-rns!

@jgwest
jgwest merged commit 2312d50 into argoproj-labs:main Sep 24, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Race condition when Application is deleted simultaneously on both principal/managed agent: creates undeletable Application on managed-agent

4 participants