Skip to content

Fixed stale MDM profiles after MDM toggle - #43719

Merged
getvictor merged 3 commits into
mainfrom
victor/42427-cleanup-stale-mdm-profiles
Apr 20, 2026
Merged

Fixed stale MDM profiles after MDM toggle#43719
getvictor merged 3 commits into
mainfrom
victor/42427-cleanup-stale-mdm-profiles

Conversation

@getvictor

@getvictor getvictor commented Apr 17, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #42427

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.

Testing

  • Added/updated automated tests
  • QA'd all new/changed functionality manually

Summary by CodeRabbit

  • Bug Fixes
    • Pending MDM profile records are cleared when Apple or Windows MDM is turned off, preventing stale profiles from reappearing if MDM is re-enabled.
    • Pending Windows profile records are removed when a device is unenrolled, avoiding leftover pending installations.

@getvictor

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@getvictor

Copy link
Copy Markdown
Member Author

/agentic_review

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Apr 17, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Non-atomic Apple cleanup🐞 Bug ☼ Reliability
Description
CleanupAllHostMDMProfilesForPlatform performs separate non-transactional DELETEs for Apple profile
and declaration tables, so if the second DELETE fails the first one is already committed and stale
rows can remain. This can reintroduce the “stale profiles reappear after re-enable” behavior the PR
is trying to eliminate.
Code

server/datastore/mysql/mdm.go[R703-709]

+	case "darwin", "ios", "ipados":
+		if _, err := ds.writer(ctx).ExecContext(ctx, `DELETE FROM host_mdm_apple_profiles`); err != nil {
+			return ctxerr.Wrap(ctx, err, "deleting all rows from host_mdm_apple_profiles")
+		}
+		if _, err := ds.writer(ctx).ExecContext(ctx, `DELETE FROM host_mdm_apple_declarations`); err != nil {
+			return ctxerr.Wrap(ctx, err, "deleting all rows from host_mdm_apple_declarations")
+		}
Evidence
The new cleanup method deletes from host_mdm_apple_profiles and host_mdm_apple_declarations via two
independent ExecContext calls with no transaction, so partial success is possible. Elsewhere in the
datastore, multi-statement cleanup that must be consistent is wrapped in a withRetryTxx transaction
to guarantee atomicity.

server/datastore/mysql/mdm.go[699-719]
server/datastore/mysql/apple_mdm.go[6932-6936]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`CleanupAllHostMDMProfilesForPlatform` runs multiple DELETE statements for Apple tables without a transaction. If one DELETE succeeds and the next fails, the database is left partially cleaned.
### Issue Context
This cleanup is invoked when MDM is disabled globally (e.g., APNS cert deletion) to prevent stale host-profile state from persisting.
### Fix Focus Areas
- server/datastore/mysql/mdm.go[699-719]
### Suggested change
- Wrap the platform cleanup in `ds.withRetryTxx(...)`.
- Use the provided `tx` for all DELETEs in the selected platform case so they commit/rollback together.
- (Optional) Keep Windows cleanup in the same pattern for consistency.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Mock cleanup func panic🐞 Bug ≡ Correctness
Description
Service now calls CleanupAllHostMDMProfilesForPlatform when deleting the Apple APNS cert, but
mock.Store will panic if CleanupAllHostMDMProfilesForPlatformFunc is not set.
TestMDMAppleAuthorization sets other MDM-related mock functions and calls DeleteMDMAppleAPNSCert
for admin users, so it will hit this new call and panic at runtime.
Code

server/service/mdm.go[R3391-3394]

+	// Clean up all pending Apple MDM profile rows since hosts can no longer receive MDM commands.
+	if err := svc.ds.CleanupAllHostMDMProfilesForPlatform(ctx, "darwin"); err != nil {
+		return ctxerr.Wrap(ctx, err, "cleaning up Apple host MDM profiles")
+	}
Evidence
The service’s DeleteMDMAppleAPNSCert path now invokes the new datastore method. The mock datastore
implementation calls the function pointer directly (no nil guard), and the existing authorization
test configures many required mock functions but does not configure
CleanupAllHostMDMProfilesForPlatformFunc before calling svc.DeleteMDMAppleAPNSCert for admin
users.

server/service/mdm.go[3391-3394]
server/mock/datastore_mock.go[5692-5697]
server/service/mdm_test.go[149-184]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`mock.Store` will panic if `CleanupAllHostMDMProfilesForPlatformFunc` is nil, but the new service call sites execute it in existing unit tests.
### Issue Context
`TestMDMAppleAuthorization` calls `svc.DeleteMDMAppleAPNSCert` for admin users and sets several datastore mock funcs, but does not set `CleanupAllHostMDMProfilesForPlatformFunc`.
### Fix Focus Areas
- server/service/mdm_test.go[149-184]
- server/mock/datastore_mock.go[5692-5697]
### Suggested change
- In affected tests (at least `TestMDMAppleAuthorization`), add:
- `ds.CleanupAllHostMDMProfilesForPlatformFunc = func(ctx context.Context, platform string) error { return nil }`
- (Optional, broader safety) Update the mock method to return a clear error when the func is nil (or provide a default no-op), to avoid panics and make failures easier to debug.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread server/datastore/mysql/mdm.go Outdated
Comment thread server/service/mdm.go
@coderabbitai

coderabbitai Bot commented Apr 17, 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: f8a331bd-26e8-4181-a54e-e1cb8e53f10c

📥 Commits

Reviewing files that changed from the base of the PR and between 7a757dd and 111a29f.

📒 Files selected for processing (1)
  • server/datastore/mysql/microsoft_mdm.go

Walkthrough

Adds Datastore.CleanupAllHostMDMProfilesForPlatform(ctx, platform string) to bulk-delete host MDM profile rows by platform. The MySQL implementation deletes platform-specific tables (Apple: host_mdm_apple_profiles and host_mdm_apple_declarations; Windows: host_mdm_windows_profiles). The datastore interface and mock were extended. Calls to the new method were added when Apple or Windows MDM is disabled globally and from the Windows unenroll path; tests were updated to assert profile rows are cleared.

Possibly related PRs

🚥 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fixed stale MDM profiles after MDM toggle' clearly summarizes the main change, directly addressing the core objective of preventing stale MDM profile rows from persisting when MDM is toggled off.
Description check ✅ Passed The description includes the related issue number (#42427), explicitly checks the required checklist items (changes file added, automated tests added, manual QA), and provides sufficient context linking to the issue for implementation details.
Linked Issues check ✅ Passed The pull request fully addresses the requirements in issue #42427: it implements global MDM toggle cleanup (Windows and Apple), per-host unenrollment cleanup, and includes automated tests verifying profile row cleanup in both scenarios.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the issue objectives: adding cleanup methods, implementing cleanup triggers in MDM toggle and unenrollment flows, updating tests, and adding the required changes file. No extraneous modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch victor/42427-cleanup-stale-mdm-profiles

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/service/appconfig.go (1)

1133-1143: ⚠️ Potential issue | 🟠 Major

Cleanup becomes non-retriable after partial success

Line 1133 gates cleanup on a state transition only. Because config is already saved earlier, a failure at Line 1141 returns an error but leaves Windows MDM disabled; a retry with the same payload won’t hit this branch again, so stale rows can remain indefinitely.

Suggested fix
-	// if Windows MDM was enabled or disabled, create the corresponding activity
-	if oldAppConfig.MDM.WindowsEnabledAndConfigured != appConfig.MDM.WindowsEnabledAndConfigured {
+	windowsMDMEnabledChanged := oldAppConfig.MDM.WindowsEnabledAndConfigured != appConfig.MDM.WindowsEnabledAndConfigured
+	windowsMDMDisabled := !appConfig.MDM.WindowsEnabledAndConfigured
+
+	// Run cleanup whenever Windows MDM is disabled so retries can recover from partial failures.
+	if windowsMDMDisabled {
+		if err := svc.ds.CleanupAllHostMDMProfilesForPlatform(ctx, "windows"); err != nil {
+			return nil, ctxerr.Wrap(ctx, err, "cleaning up Windows host MDM profiles")
+		}
+	}
+
+	// if Windows MDM was enabled or disabled, create the corresponding activity
+	if windowsMDMEnabledChanged {
 		var act fleet.ActivityDetails
 		if appConfig.MDM.WindowsEnabledAndConfigured {
 			act = fleet.ActivityTypeEnabledWindowsMDM{}
 		} else {
 			act = fleet.ActivityTypeDisabledWindowsMDM{}
-
-			// Clean up all pending Windows MDM profile rows since hosts can no longer receive MDM commands.
-			if err := svc.ds.CleanupAllHostMDMProfilesForPlatform(ctx, "windows"); err != nil {
-				return nil, ctxerr.Wrap(ctx, err, "cleaning up Windows host MDM profiles")
-			}
 		}
 		if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil {
 			return nil, ctxerr.Wrapf(ctx, err, "create activity %s", act.ActivityName())
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/service/appconfig.go` around lines 1133 - 1143, The cleanup of Windows
MDM profiles is only attempted during the state transition and a failure returns
an error after the config has already been saved, making the operation
non-retriable; change the logic around
oldAppConfig.MDM.WindowsEnabledAndConfigured vs
appConfig.MDM.WindowsEnabledAndConfigured so that when Windows is being disabled
you either (a) perform CleanupAllHostMDMProfilesForPlatform(ctx, "windows")
before persisting the new appConfig, or (b) if cleanup must run after save, do
not return a hard error on svc.ds.CleanupAllHostMDMProfilesForPlatform
failure—instead record/log the error and enqueue or schedule a retry/background
job to run the same svc.ds.CleanupAllHostMDMProfilesForPlatform call (or persist
a pending-cleanup flag) so the cleanup is retriable; adjust handling around
Activity creation (fleet.ActivityTypeDisabledWindowsMDM) accordingly.
🧹 Nitpick comments (2)
server/datastore/mysql/mdm.go (1)

703-713: Make Apple cleanup atomic to prevent partial stale state.

Line 704 and Line 707 execute separate deletes without a transaction. If the second delete fails, cleanup is only partially applied.

Proposed change
 func (ds *Datastore) CleanupAllHostMDMProfilesForPlatform(ctx context.Context, platform string) error {
-	// Each platform case uses literal SQL statements to avoid fmt.Sprintf with table names (gosec G202).
-	// Apple platforms (darwin, ios, ipados) share the same profile/declaration tables.
-	switch platform {
-	case "darwin", "ios", "ipados":
-		if _, err := ds.writer(ctx).ExecContext(ctx, `DELETE FROM host_mdm_apple_profiles`); err != nil {
-			return ctxerr.Wrap(ctx, err, "deleting all rows from host_mdm_apple_profiles")
-		}
-		if _, err := ds.writer(ctx).ExecContext(ctx, `DELETE FROM host_mdm_apple_declarations`); err != nil {
-			return ctxerr.Wrap(ctx, err, "deleting all rows from host_mdm_apple_declarations")
-		}
-	case "windows":
-		if _, err := ds.writer(ctx).ExecContext(ctx, `DELETE FROM host_mdm_windows_profiles`); err != nil {
-			return ctxerr.Wrap(ctx, err, "deleting all rows from host_mdm_windows_profiles")
-		}
-	default:
-		return ctxerr.Errorf(ctx, "unsupported platform %s for MDM profile cleanup", platform)
-	}
-
-	return nil
+	return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
+		switch platform {
+		case "darwin", "ios", "ipados":
+			if _, err := tx.ExecContext(ctx, `DELETE FROM host_mdm_apple_profiles`); err != nil {
+				return ctxerr.Wrap(ctx, err, "deleting all rows from host_mdm_apple_profiles")
+			}
+			if _, err := tx.ExecContext(ctx, `DELETE FROM host_mdm_apple_declarations`); err != nil {
+				return ctxerr.Wrap(ctx, err, "deleting all rows from host_mdm_apple_declarations")
+			}
+		case "windows":
+			if _, err := tx.ExecContext(ctx, `DELETE FROM host_mdm_windows_profiles`); err != nil {
+				return ctxerr.Wrap(ctx, err, "deleting all rows from host_mdm_windows_profiles")
+			}
+		default:
+			return ctxerr.Errorf(ctx, "unsupported platform %s for MDM profile cleanup", platform)
+		}
+		return nil
+	})
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/datastore/mysql/mdm.go` around lines 703 - 713, The Apple cleanup runs
two separate deletes (ds.writer(ctx).ExecContext for host_mdm_apple_profiles and
host_mdm_apple_declarations) and can leave partial state if the second fails;
wrap both delete statements in a single DB transaction (use
ds.writer(ctx).BeginTx(ctx, nil) to get tx, execute tx.ExecContext for both
DELETEs, call tx.Commit() on success and tx.Rollback() on any error) and return
ctxerr.Wrap-wrapped errors from the tx operations so the cleanup is atomic.
server/datastore/mysql/microsoft_mdm_test.go (1)

4498-4519: Broaden this test to cover remove-operation stale rows too.

Current assertions only validate cleanup of an install row. The cleanup objective includes stale install/remove operations, so this test should exercise both.

Suggested test enhancement
 	ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
 		_, err := q.ExecContext(ctx, `INSERT INTO host_mdm_windows_profiles
 				(host_uuid, status, operation_type, command_uuid, profile_name, checksum, profile_uuid)
 			VALUES (?, ?, ?, ?, ?, UNHEX(MD5('test')), ?)`,
 			host.UUID, fleet.MDMDeliveryPending, fleet.MDMOperationTypeInstall, uuid.NewString(), "TestProfile", profUUID)
+		if err != nil {
+			return err
+		}
+		_, err = q.ExecContext(ctx, `INSERT INTO host_mdm_windows_profiles
+				(host_uuid, status, operation_type, command_uuid, profile_name, checksum, profile_uuid)
+			VALUES (?, ?, ?, ?, ?, UNHEX(MD5('test2')), ?)`,
+			host.UUID, fleet.MDMDeliveryPending, fleet.MDMOperationTypeRemove, uuid.NewString(), "TestProfileRemove", profUUID)
 		return err
 	})
@@
 	winProfs, err := ds.GetHostMDMWindowsProfiles(ctx, host.UUID)
 	require.NoError(t, err)
-	require.Len(t, winProfs, 1)
+	require.Len(t, winProfs, 2)
@@
 	winProfs, err = ds.GetHostMDMWindowsProfiles(ctx, host.UUID)
 	require.NoError(t, err)
 	require.Empty(t, winProfs)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/datastore/mysql/microsoft_mdm_test.go` around lines 4498 - 4519, Add a
second test row representing a stale "remove" operation in the existing block so
the cleanup path handles both install and remove stale entries: when inserting
into host_mdm_windows_profiles via ExecAdhocSQL also insert a row with
operation_type = fleet.MDMOperationTypeRemove (and an appropriate
command_uuid/profile_uuid), then after calling
ds.MDMWindowsDeleteEnrolledDeviceWithDeviceID(ctx, deviceID) assert via
ds.GetHostMDMWindowsProfiles(ctx, host.UUID) that both the install row and the
remove-operation row are removed (i.e., return empty); keep the same setup/use
of uuid.NewString(), profUUID and existing assertions for the install row but
extend them to verify the remove-operation row existed before unenroll and is
cleaned up after.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/datastore/mysql/mdm_test.go`:
- Around line 9221-9225: After calling CleanupAllHostMDMProfilesForPlatform(ctx,
"windows") add the same assertion you did for host2 to ensure host1's Apple
profile rows survived: call ds.GetHostMDMAppleProfiles(ctx, host1.UUID), check
require.NoError(t, err) and require.NotEmpty(t, appleProfsHost1) (or equivalent
variable name) to detect any accidental partial cleanup of Apple rows for host1.

In `@server/datastore/mysql/microsoft_mdm.go`:
- Around line 238-248: The SELECT that looks up host_uuid inside the
withRetryTxx block uses `SELECT host_uuid FROM mdm_windows_enrollments WHERE
mdm_device_id = ?` and can return an arbitrary row when `mdm_device_id` is not
unique; update that query (used in the function that calls ds.withRetryTxx and
references `mdmDeviceID`) to mirror the pattern in
`MDMWindowsGetEnrolledDeviceWithDeviceID` by adding `ORDER BY created_at DESC
LIMIT 1` so you deterministically pick the latest enrollment before performing
the subsequent `DELETE` and profile cleanup.

---

Outside diff comments:
In `@server/service/appconfig.go`:
- Around line 1133-1143: The cleanup of Windows MDM profiles is only attempted
during the state transition and a failure returns an error after the config has
already been saved, making the operation non-retriable; change the logic around
oldAppConfig.MDM.WindowsEnabledAndConfigured vs
appConfig.MDM.WindowsEnabledAndConfigured so that when Windows is being disabled
you either (a) perform CleanupAllHostMDMProfilesForPlatform(ctx, "windows")
before persisting the new appConfig, or (b) if cleanup must run after save, do
not return a hard error on svc.ds.CleanupAllHostMDMProfilesForPlatform
failure—instead record/log the error and enqueue or schedule a retry/background
job to run the same svc.ds.CleanupAllHostMDMProfilesForPlatform call (or persist
a pending-cleanup flag) so the cleanup is retriable; adjust handling around
Activity creation (fleet.ActivityTypeDisabledWindowsMDM) accordingly.

---

Nitpick comments:
In `@server/datastore/mysql/mdm.go`:
- Around line 703-713: The Apple cleanup runs two separate deletes
(ds.writer(ctx).ExecContext for host_mdm_apple_profiles and
host_mdm_apple_declarations) and can leave partial state if the second fails;
wrap both delete statements in a single DB transaction (use
ds.writer(ctx).BeginTx(ctx, nil) to get tx, execute tx.ExecContext for both
DELETEs, call tx.Commit() on success and tx.Rollback() on any error) and return
ctxerr.Wrap-wrapped errors from the tx operations so the cleanup is atomic.

In `@server/datastore/mysql/microsoft_mdm_test.go`:
- Around line 4498-4519: Add a second test row representing a stale "remove"
operation in the existing block so the cleanup path handles both install and
remove stale entries: when inserting into host_mdm_windows_profiles via
ExecAdhocSQL also insert a row with operation_type =
fleet.MDMOperationTypeRemove (and an appropriate command_uuid/profile_uuid),
then after calling ds.MDMWindowsDeleteEnrolledDeviceWithDeviceID(ctx, deviceID)
assert via ds.GetHostMDMWindowsProfiles(ctx, host.UUID) that both the install
row and the remove-operation row are removed (i.e., return empty); keep the same
setup/use of uuid.NewString(), profUUID and existing assertions for the install
row but extend them to verify the remove-operation row existed before unenroll
and is cleaned up after.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 64d09e99-a84c-44e7-a460-07ab73c04c8e

📥 Commits

Reviewing files that changed from the base of the PR and between 0fcb36c and fbbb426.

📒 Files selected for processing (9)
  • changes/42427-cleanup-stale-mdm-profiles
  • server/datastore/mysql/mdm.go
  • server/datastore/mysql/mdm_test.go
  • server/datastore/mysql/microsoft_mdm.go
  • server/datastore/mysql/microsoft_mdm_test.go
  • server/fleet/datastore.go
  • server/mock/datastore_mock.go
  • server/service/appconfig.go
  • server/service/mdm.go

Comment thread server/datastore/mysql/mdm_test.go
Comment thread server/datastore/mysql/microsoft_mdm.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes stale host-level MDM profile state that could persist in the DB when MDM is turned off (or when a Windows device unenrolls), preventing “ghost” pending operations from reappearing when MDM is re-enabled.

Changes:

  • Add a datastore method to delete all host MDM profile rows for a given platform and invoke it when disabling Windows MDM / deleting Apple APNS cert.
  • Ensure Windows unenrollment deletes any host_mdm_windows_profiles rows for the unenrolled host.
  • Add MySQL datastore tests covering Windows unenroll cleanup and global cleanup behavior; add a user-visible changes entry.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
server/service/mdm.go Cleanup Apple host MDM profile state when APNS cert is deleted (Apple MDM disabled).
server/service/appconfig.go Cleanup Windows host MDM profile state when Windows MDM is toggled off.
server/fleet/datastore.go Extend datastore interface with platform-wide host-profile cleanup method.
server/datastore/mysql/mdm.go Implement platform-wide cleanup (Apple profiles/declarations, Windows profiles).
server/datastore/mysql/microsoft_mdm.go Cleanup Windows host profile rows during device unenrollment transaction.
server/mock/datastore_mock.go Add mock hook for new datastore interface method.
server/datastore/mysql/mdm_test.go Add test coverage for the new cleanup method.
server/datastore/mysql/microsoft_mdm_test.go Add test coverage for Windows unenroll profile cleanup.
changes/42427-cleanup-stale-mdm-profiles Add changelog entry for the bugfix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/service/appconfig.go
@codecov

codecov Bot commented Apr 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 40.54054% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.91%. Comparing base (ce3fd02) to head (111a29f).
⚠️ Report is 28 commits behind head on main.

Files with missing lines Patch % Lines
server/datastore/mysql/microsoft_mdm.go 47.36% 6 Missing and 4 partials ⚠️
server/datastore/mysql/mdm.go 42.85% 5 Missing and 3 partials ⚠️
server/service/appconfig.go 0.00% 1 Missing and 1 partial ⚠️
server/service/mdm.go 0.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #43719      +/-   ##
==========================================
- Coverage   66.92%   66.91%   -0.01%     
==========================================
  Files        2600     2600              
  Lines      208710   208742      +32     
  Branches     9339     9339              
==========================================
+ Hits       139678   139680       +2     
- Misses      56320    56337      +17     
- Partials    12712    12725      +13     
Flag Coverage Δ
backend 68.70% <40.54%> (-0.02%) ⬇️

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

☔ View full report in Codecov by Sentry.
📢 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@getvictor
getvictor marked this pull request as ready for review April 17, 2026 19:03
@getvictor
getvictor requested a review from a team as a code owner April 17, 2026 19:03

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
server/datastore/mysql/mdm.go (1)

701-715: Consider handling "android" for symmetry / future-proofing.

host_mdm_android_profiles exists and is referenced elsewhere in this file (e.g. lines 877, 1944, 2218). If Android MDM is ever disabled globally, this helper will return "unsupported platform" instead of cleaning stale rows. If that's intentional (Android MDM toggle doesn't reuse this path), ignore; otherwise add an "android" case deleting from host_mdm_android_profiles.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/datastore/mysql/mdm.go` around lines 701 - 715, The switch over
platform in the MDM cleanup (the block using tx.ExecContext and ctxerr.Wrap)
currently handles "darwin"/"ios"/"ipados" and "windows" but not "android"; add a
case "android" that executes a DELETE on host_mdm_android_profiles and wraps
errors like the other cases (use tx.ExecContext(ctx, `DELETE FROM
host_mdm_android_profiles`) and return ctxerr.Wrap on error) so stale Android
MDM rows are cleaned the same way as the other platforms.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/datastore/mysql/microsoft_mdm.go`:
- Around line 235-266: The comment in MDMWindowsDeleteEnrolledDeviceWithDeviceID
misleadingly says "pending profile rows" while the SQL DELETE on
host_mdm_windows_profiles removes all rows for the host; update the comment to
state that all host MDM profile rows are deleted (stale rows are wiped) or
otherwise clarify that the DELETE is unqualified and intentionally removes all
statuses, referencing the tx.ExecContext call that runs `DELETE FROM
host_mdm_windows_profiles WHERE host_uuid = ?` so future readers understand the
behavior.
- Around line 240-248: The code reads host_uuid into a plain string which will
cause a scan error when host_uuid is NULL; change the scan target to
sql.NullString (e.g., hostUUIDNull) in the SELECT using sqlx.GetContext, then
convert it to the string you need (use hostUUIDNull.Valid ? hostUUIDNull.String
: "" or treat as absent) before continuing so NULL host_uuid does not abort
unenrollment; update any subsequent uses of hostUUID in the Microsoft MDM
unenrollment flow in microsoft_mdm.go accordingly.

---

Nitpick comments:
In `@server/datastore/mysql/mdm.go`:
- Around line 701-715: The switch over platform in the MDM cleanup (the block
using tx.ExecContext and ctxerr.Wrap) currently handles "darwin"/"ios"/"ipados"
and "windows" but not "android"; add a case "android" that executes a DELETE on
host_mdm_android_profiles and wraps errors like the other cases (use
tx.ExecContext(ctx, `DELETE FROM host_mdm_android_profiles`) and return
ctxerr.Wrap on error) so stale Android MDM rows are cleaned the same way as the
other platforms.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a6bb9017-1ba7-4a44-bfc4-b4c4a784f33e

📥 Commits

Reviewing files that changed from the base of the PR and between fbbb426 and 7a757dd.

📒 Files selected for processing (4)
  • server/datastore/mysql/mdm.go
  • server/datastore/mysql/mdm_test.go
  • server/datastore/mysql/microsoft_mdm.go
  • server/service/mdm_test.go

Comment thread server/datastore/mysql/microsoft_mdm.go Outdated
Comment thread server/datastore/mysql/microsoft_mdm.go Outdated

@JordanMontgomery JordanMontgomery left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One small comment but I think this is probably OK

Comment thread server/service/mdm.go
}

// Clean up all pending Apple MDM profile rows since hosts can no longer receive MDM commands.
if err := svc.ds.CleanupAllHostMDMProfilesForPlatform(ctx, "darwin"); err != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am OK with this change but there is one potential downside which is that in the event a customer accidentally turns off Apple MDM it goes from being something that is reversible with a few DB queries to something that requires a full DB restore(as happened recently in dogfood). This is probably OK but may be worth being aware of. IIRC that's why it wasn't originally done but I don't think it's necessarily a reason not to do it. It might be worth asking if that's a customer concern at all

@getvictor
getvictor merged commit b6bacca into main Apr 20, 2026
51 checks passed
@getvictor
getvictor deleted the victor/42427-cleanup-stale-mdm-profiles branch April 20, 2026 14:23
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.

Stale MDM pending profile rows persist after Apple/Windows MDM is turned off

5 participants