Skip to content

CP: Fix host activity queues blocked by stuck app installs (#51197) - #51378

Merged
jkatz01 merged 1 commit into
rc-minor-fleet-v4.91.0from
51197-cherry-pick
Aug 17, 2026
Merged

CP: Fix host activity queues blocked by stuck app installs (#51197)#51378
jkatz01 merged 1 commit into
rc-minor-fleet-v4.91.0from
51197-cherry-pick

Conversation

@jkatz01

@jkatz01 jkatz01 commented Aug 17, 2026

Copy link
Copy Markdown
Member

Cherry-pick of #51197 into rc-minor-fleet-v4.91.0. Resolves #50681.

Testing

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

**Related issue:** Resolves #50681

An App Store or in-house app install that a device acknowledges but
never verifies holds the head of the host's activity queue for good.
Fleet records these installs as successful only on verification, so
nothing behind one runs, scripts and package installs included, and
`UnblockHostsUpcomingActivityQueue` cannot rescue the host because
something is activated. One reported host held 1 activated install and
1,328 waiting behind it, unchanged for 72 days.

- Added `ReapStuckActivatedMDMInstalls`, which fails App Store and
in-house app installs that have been activated past a timeout and can no
longer make progress, then releases the queue.
- Ran it from a new `reap_stuck_activated_mdm_installs` job on the
existing `upcoming_activities_maintenance` schedule.
- Added `server.vpp_install_reap_timeout`, default 24h.
- Released the verification command when a result arrives with nothing
left to verify. It was previously held until the daily cleanup,
suppressing the next install's verification on that host.

## Notes for reviewers

- An install is reaped only once it cannot make progress on its own. An
answered install is judged on the age of the answer alone. Only an
unanswered one is judged on delivery, having either lost its queue row
or gone past the seven-day push window. Age alone would fail every
install to a device that is merely switched off, and judging an answered
install on delivery would fail one a returning device had just started
running. A `NotNow` reply is not an answer, since nanomdm keeps the
command queued and re-serves it.
- The delivery window is measured from `activated_at`, not from
`nano_enrollment_queue.created_at`. Both enqueue paths copy the queue
row's `created_at` from the activity's to preserve ordering, so a
command that activates after a long wait is born outside the window.
That is the state of every install behind a head this fix has just
freed.
- An acknowledged install ages from `ncr.updated_at`, the same column
the verify handler measures its own budget from, so a device that
returns after a long absence keeps its verification window.
- Reaping is per host. Activation batches up to five installs and stops
at the first row still activated, so failing one of a batch would
advance nothing.
- No migration. The reaper clears existing stuck installs on its first
pass.
- Scan cost on MySQL 8.0.44: 23 ms with 24,300 queued App Store
installs, 253 ms with 250,300, and 217 ms with nothing stuck. It is an
index range scan on `(activity_type, host_id)`, and the subqueries run
once per reap candidate rather than once per row.

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)
- [x] QA'd all new/changed functionality manually

The datastore test seeds fifteen hosts covering each way an install does
or does not qualify, and asserts that reaping one host leaves the others
untouched. Every assertion that claims to catch the defect was checked
by reverting the fix and confirming it fails on the expected assertion.

Manual QA ran the final predicate against a copy of a dev database at
the default timeout, with six synthetic iPadOS hosts covering one rule
each, all activated well past the floor so only the answer and delivery
rules decided. The two that could no longer make progress were reaped,
releasing their queues and deactivating their nano rows; on the stuck
host the verification command was released and the script queued behind
the install activated. The four still in flight were left alone:
unanswered but recently queued, answered `NotNow`, carrying a backdated
queue row, and returning after a long absence to acknowledge. Both
config paths were exercised at startup, one below the verify timeout and
one at zero. In-house installs, the sub-second timeout, and the
duplicate-UUID lock delete rest on the automated tests above.

## New Fleet configuration settings

- [x] Setting(s) is/are explicitly excluded from GitOps


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Automatically cleans up App Store and in-house app installations stuck
for more than 24 hours.
* Adds a configurable cleanup timeout, which can be disabled when set to
zero.
  * Preserves commands that have not yet been delivered to devices.
* **Bug Fixes**
* Clears obsolete verification commands and releases blocked activity
queues so subsequent installations can proceed.
* Handles duplicated host identifiers correctly when removing stale
commands.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@jkatz01

jkatz01 commented Aug 17, 2026

Copy link
Copy Markdown
Member Author

I believe the test failures are unrelated and due to upstream vulnerability data.

@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.12299% with 54 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (rc-minor-fleet-v4.91.0@1ba789a). Learn more about missing BASE report.

Files with missing lines Patch % Lines
server/datastore/mysql/activities.go 76.31% 16 Missing and 11 partials ⚠️
cmd/fleet/cron.go 19.04% 14 Missing and 3 partials ⚠️
server/service/mdm_install_reaper.go 76.47% 5 Missing and 3 partials ⚠️
server/datastore/mysql/apple_mdm.go 75.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@                    Coverage Diff                    @@
##             rc-minor-fleet-v4.91.0   #51378   +/-   ##
=========================================================
  Coverage                          ?   68.56%           
=========================================================
  Files                             ?     3982           
  Lines                             ?   256588           
  Branches                          ?    13805           
=========================================================
  Hits                              ?   175935           
  Misses                            ?    65008           
  Partials                          ?    15645           
Flag Coverage Δ
backend 69.67% <71.12%> (?)

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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jkatz01
jkatz01 marked this pull request as ready for review August 17, 2026 19:39
@jkatz01
jkatz01 requested a review from a team as a code owner August 17, 2026 19:39
@jkatz01
jkatz01 merged commit 1e47dc5 into rc-minor-fleet-v4.91.0 Aug 17, 2026
49 of 54 checks passed
@jkatz01
jkatz01 deleted the 51197-cherry-pick branch August 17, 2026 20:32
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.

3 participants