Skip to content

Added automatic Android cert retry#42734

Merged
getvictor merged 16 commits intomainfrom
victor/37546-android-cert-retry
Apr 1, 2026
Merged

Added automatic Android cert retry#42734
getvictor merged 16 commits intomainfrom
victor/37546-android-cert-retry

Conversation

@getvictor
Copy link
Copy Markdown
Member

@getvictor getvictor commented Mar 31, 2026

Related issue: Resolves #37546

Docs: #42780
Demo: https://www.youtube.com/watch?v=K44wRg9_79M

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.
    See Changes files for more information.

Testing

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

Database migrations

  • Checked schema for all modified table for columns that will auto-update timestamps during migration.

Summary by CodeRabbit

  • New Features
    • Automatic retry for Android certificate installations: failed installs are retried up to 3 times before marked terminal.
    • Installation activities recorded: install/failed-install events (with details) are logged for better visibility and troubleshooting.
    • Resend/reset actions now reset retry state so retries behave predictably after manual resend.

@getvictor
Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements automatic retry behavior for Android certificate template installs, adding persistent retry tracking and tests to validate retry/terminal failure behavior. Resolves #37546.

Changes:

  • Added retry_count to host_certificate_templates (schema + migration) and surfaced it in HostCertificateTemplate.
  • Added datastore support to reset failed installs back to pending and increment retry count for automatic retries.
  • Updated the certificate status update service flow and expanded integration/datastore tests around retries, terminal failure, and resend semantics.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
server/service/certificates.go Adds automatic retry logic on install failure and attempts to log install activities.
server/datastore/mysql/host_certificate_templates.go Adds RetryHostCertificateTemplate and includes retry_count in record retrieval + renewal reset.
server/datastore/mysql/certificate_templates.go Updates resend logic to set retry_count to max (single post-resend attempt semantics).
server/fleet/host_certificate_template.go Introduces MaxCertificateInstallRetries and RetryCount field on the model.
server/fleet/datastore.go Extends datastore interface with RetryHostCertificateTemplate.
server/mock/datastore_mock.go Adds mock support for the new datastore method.
server/datastore/mysql/schema.sql Adds retry_count column and bumps migration status table seed.
server/datastore/mysql/migrations/tables/20260331000000_AddCertRetryCount.go Migration to add retry_count column.
server/datastore/mysql/host_certificate_templates_test.go Adds unit test coverage for retry reset behavior and challenge deletion.
server/service/integration_android_certificate_templates_test.go Adds integration coverage for auto-retry/terminal failure/resend behavior.
changes/37546-android-certificate-install-activity Adds a changelog entry for the retry behavior.

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

Comment thread server/service/certificates.go Outdated
Comment thread server/service/certificates.go
Comment thread server/datastore/mysql/host_certificate_templates.go Outdated
Comment thread server/datastore/mysql/host_certificate_templates.go Outdated
Comment thread server/service/integration_android_certificate_templates_test.go Outdated
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

Walkthrough

This PR adds automatic retry logic for Android certificate installation failures: a new retry_count column (default 0) on host_certificate_templates, a MaxCertificateInstallRetries = 3 constant, a datastore method RetryHostCertificateTemplate, schema migration, and updates to UpdateCertificateStatus to create installation activities and trigger automatic retries (incrementing retry_count) up to the max before marking terminal failures. Tests and integration scenarios exercise the retry incrementing, challenge deletion, terminal-failure behavior, and successful recovery.

Possibly related PRs

  • fleetdm/fleet PR 36796 — Touches certificate delivery status handling and modifies UpdateCertificateStatus (directly related to the changes adding retry-triggering logic).
  • fleetdm/fleet PR 37959 — Modifies Android certificate template renewal/update logic including SetAndroidCertificateTemplatesForRenewal (related to added retry_count handling in that update).
  • fleetdm/fleet PR 36258 — Alters host_certificate_templates datastore/service logic for Android certificates (overlaps on schema/datastore changes and host-template workflows).
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description is incomplete. Multiple required checklist items are missing or unchecked, and critical sections lack detail. Complete the full checklist: confirm input validation (SQL injection prevention), endpoint compatibility, automated tests coverage with host isolation, manual QA, database migration review (timestamp/collation), and fleetd/orbit/Fleet Desktop compatibility checks. Add implementation details if available.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: automatic Android certificate retry logic. It's concise and clearly reflects the primary objective of the PR.
Linked Issues check ✅ Passed The PR successfully implements the core engineering objective from issue #37546: automatic retry up to 3 times for Android certificate installation with retry count tracking, datastore methods, and comprehensive testing.
Out of Scope Changes check ✅ Passed All code changes directly support the retry functionality: database migration, model updates, datastore layer, service logic, activity tracking, and integration tests. No extraneous changes 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/37546-android-cert-retry

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
server/datastore/mysql/certificate_templates.go (1)

405-423: ⚠️ Potential issue | 🟠 Major

Reset the retry budget on resend.

Line 418 writes fleet.MaxCertificateInstallRetries back into retry_count, so the next install failure after a manual resend is treated as terminal and gets zero automatic retries. That makes a resend a one-shot attempt instead of starting a fresh delivery cycle.

Proposed fix
 func (ds *Datastore) ResendHostCertificateTemplate(ctx context.Context, hostID uint, templateID uint) error {
-	stmt := fmt.Sprintf(`
+	const stmt = `
 		UPDATE
 			host_certificate_templates hct
 		INNER JOIN
 			hosts h ON h.uuid = hct.host_uuid
 		SET
 			hct.uuid = UUID_TO_BIN(UUID(), true),
 			hct.fleet_challenge = NULL,
 			hct.not_valid_before = NULL,
 			hct.not_valid_after = NULL,
 			hct.serial = NULL,
 			hct.detail = NULL,
-			hct.retry_count = %d,
+			hct.retry_count = 0,
 			hct.status = ?
 		WHERE
 			h.id = ? AND
 			hct.certificate_template_id = ?
-		`, fleet.MaxCertificateInstallRetries)
+	`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/datastore/mysql/certificate_templates.go` around lines 405 - 423, The
ResendHostCertificateTemplate SQL resets retry_count incorrectly to
fleet.MaxCertificateInstallRetries which makes the next install failure treated
as terminal; change the UPDATE in ResendHostCertificateTemplate to reset
hct.retry_count back to 0 (or the initial starting value your delivery cycle
expects) instead of fleet.MaxCertificateInstallRetries so a manual resend
restarts the delivery retry budget; locate the UPDATE that touches
host_certificate_templates / hct.retry_count in the
ResendHostCertificateTemplate function and replace the injected
fleet.MaxCertificateInstallRetries with 0 (or the configured initial retry
counter) and run tests.
🧹 Nitpick comments (1)
server/datastore/mysql/host_certificate_templates_test.go (1)

2012-2077: Good retry-flow coverage; add assertions for cleared challenge and regenerated UUID.

This test already checks the key state transitions well. Consider also asserting record.FleetChallenge == nil and UUID rotation after retry to fully lock down the retry contract.

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

In `@server/datastore/mysql/host_certificate_templates_test.go` around lines 2012
- 2077, Add assertions in testRetryHostCertificateTemplate to ensure the
FleetChallenge is cleared and the template UUID is rotated on retry: before
calling ds.RetryHostCertificateTemplate capture the initial record's
FleetChallenge and UUID via ds.GetHostCertificateTemplateRecord, then after the
first RetryHostCertificateTemplate call assert record.FleetChallenge == nil (or
record.FleetChallenge pointer is nil) and assert the record.UUID (or the unique
identifier field returned by GetHostCertificateTemplateRecord) is different from
the captured initial UUID; use the same helpers
(GetHostCertificateTemplateRecord and RetryHostCertificateTemplate) to locate
and verify these values.
🤖 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/host_certificate_templates.go`:
- Around line 175-205: The RetryHostCertificateTemplate function currently
performs the DELETE and UPDATE outside a transaction and does not verify the
UPDATE actually affected a row; change it to run both statements inside a single
transaction (use ds.writer(ctx).BeginTx / tx := ds.writer(ctx).BeginTx or
equivalent to obtain a tx and use tx.ExecContext for both statements), ensure
you call tx.Rollback on error and tx.Commit on success, and after the UPDATE
check the sql.Result.RowsAffected value and return an error (wrapped with
ctxerr.Wrap) if zero rows were affected so missed target rows are surfaced; keep
existing error messages and preserve the detail parameter handling and UUID
logic when moving the statements into the transaction.

In `@server/service/certificates.go`:
- Around line 699-741: The activity for failed installs is being created before
the retry/DB update, causing transient failures to be logged; change the flow so
svc.NewActivity (creating CertificateActivityInstalled /
CertificateActivityFailedInstall) is only called after the datastore terminal
write completes — i.e., run RetryHostCertificateTemplate and/or
UpsertCertificateStatus first (use svc.ds.RetryHostCertificateTemplate and the
UpsertCertificateStatus path that writes the final record), then, only when the
status is terminal (MDMDeliveryVerified or MDMDeliveryFailed with
record.RetryCount >= fleet.MaxCertificateInstallRetries), call svc.NewActivity
to emit the installed_certificate/failed_install activity (using
CertificateActivityFailedInstall, MDMDeliveryFailed, record.RetryCount and
MaxCertificateInstallRetries to determine terminality) and handle/log errors as
before.

In `@server/service/integration_android_certificate_templates_test.go`:
- Around line 1621-1633: The current check in TestCertificateTemplateResend only
matches act.Type and can pick an earlier successful installed_certificate
activity; instead, after fetching hostActivitiesResp (listActivitiesResponse)
locate the most recent activity for ActivityTypeInstalledCertificate by
inspecting hostActivitiesResp.Activities ordered by created_at (or use index 0
if response is newest-first), then assert the activity's payload/status/detail
indicates failure (e.g., payload.Status == "failed_install" or payload.Detail
contains the failure message) rather than just matching act.Type; update the
verification using the activity struct fields (from listActivitiesResponse/act)
to confirm the terminal failed_install record is logged.

---

Outside diff comments:
In `@server/datastore/mysql/certificate_templates.go`:
- Around line 405-423: The ResendHostCertificateTemplate SQL resets retry_count
incorrectly to fleet.MaxCertificateInstallRetries which makes the next install
failure treated as terminal; change the UPDATE in ResendHostCertificateTemplate
to reset hct.retry_count back to 0 (or the initial starting value your delivery
cycle expects) instead of fleet.MaxCertificateInstallRetries so a manual resend
restarts the delivery retry budget; locate the UPDATE that touches
host_certificate_templates / hct.retry_count in the
ResendHostCertificateTemplate function and replace the injected
fleet.MaxCertificateInstallRetries with 0 (or the configured initial retry
counter) and run tests.

---

Nitpick comments:
In `@server/datastore/mysql/host_certificate_templates_test.go`:
- Around line 2012-2077: Add assertions in testRetryHostCertificateTemplate to
ensure the FleetChallenge is cleared and the template UUID is rotated on retry:
before calling ds.RetryHostCertificateTemplate capture the initial record's
FleetChallenge and UUID via ds.GetHostCertificateTemplateRecord, then after the
first RetryHostCertificateTemplate call assert record.FleetChallenge == nil (or
record.FleetChallenge pointer is nil) and assert the record.UUID (or the unique
identifier field returned by GetHostCertificateTemplateRecord) is different from
the captured initial UUID; use the same helpers
(GetHostCertificateTemplateRecord and RetryHostCertificateTemplate) to locate
and verify these values.
🪄 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: 201481b3-3ee9-4874-805b-ec201936a77b

📥 Commits

Reviewing files that changed from the base of the PR and between 99a0413 and af334c2.

📒 Files selected for processing (11)
  • changes/37546-android-certificate-install-activity
  • server/datastore/mysql/certificate_templates.go
  • server/datastore/mysql/host_certificate_templates.go
  • server/datastore/mysql/host_certificate_templates_test.go
  • server/datastore/mysql/migrations/tables/20260331000000_AddCertRetryCount.go
  • server/datastore/mysql/schema.sql
  • server/fleet/datastore.go
  • server/fleet/host_certificate_template.go
  • server/mock/datastore_mock.go
  • server/service/certificates.go
  • server/service/integration_android_certificate_templates_test.go

Comment thread server/datastore/mysql/host_certificate_templates.go
Comment thread server/service/certificates.go Outdated
Comment thread server/service/integration_android_certificate_templates_test.go Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 31, 2026

Codecov Report

❌ Patch coverage is 79.16667% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.82%. Comparing base (205afee) to head (72e7a80).
⚠️ Report is 28 commits behind head on main.

Files with missing lines Patch % Lines
...rver/datastore/mysql/host_certificate_templates.go 85.18% 2 Missing and 2 partials ⚠️
...rations/tables/20260331000000_AddCertRetryCount.go 55.55% 3 Missing and 1 partial ⚠️
server/service/certificates.go 77.77% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #42734      +/-   ##
==========================================
+ Coverage   66.80%   66.82%   +0.01%     
==========================================
  Files        2541     2542       +1     
  Lines      203737   204013     +276     
  Branches     9279     9279              
==========================================
+ Hits       136107   136332     +225     
- Misses      55307    55333      +26     
- Partials    12323    12348      +25     
Flag Coverage Δ
backend 68.62% <79.16%> (+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
Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
server/service/certificates.go (1)

699-744: ⚠️ Potential issue | 🟠 Major

Persist the retry/final state before creating the certificate activity.

ActivityTypeInstalledCertificate is written before either RetryHostCertificateTemplate or UpsertCertificateStatus runs, so a later datastore error can leave an installed/failed_install activity for a transition that never committed. It also records failed_install before the code knows whether the failure is terminal or just being queued for retry.

💡 Suggested structure
-	// Log activity for install statuses (not removals). Failures are logged on every attempt
-	// (including retries) so IT admins have visibility into retry attempts.
-	if update.OperationType == fleet.MDMOperationTypeInstall {
-		var actStatus fleet.CertificateActivityStatus
-		switch update.Status {
-		case fleet.MDMDeliveryVerified:
-			actStatus = fleet.CertificateActivityInstalled
-		case fleet.MDMDeliveryFailed:
-			actStatus = fleet.CertificateActivityFailedInstall
-		}
-		if actStatus != "" {
-			detail := ""
-			if update.Detail != nil {
-				detail = *update.Detail
-			}
-			if err := svc.NewActivity(ctx, nil, fleet.ActivityTypeInstalledCertificate{
-				HostID:                host.ID,
-				HostDisplayName:       host.DisplayName(),
-				CertificateTemplateID: update.CertificateTemplateID,
-				CertificateName:       record.Name,
-				Status:                string(actStatus),
-				Detail:                detail,
-			}); err != nil {
-				// Log and continue since we don't want the client to fail on this.
-				svc.logger.ErrorContext(ctx, "failed to create certificate install activity", "host.id", host.ID, "activity.status", actStatus,
-					"err", err)
-				ctxerr.Handle(ctx, err)
-			}
-		}
-	}
-
 	// For failed installs, automatically retry if under the retry limit.
 	if update.OperationType == fleet.MDMOperationTypeInstall && update.Status == fleet.MDMDeliveryFailed {
 		if record.RetryCount < fleet.MaxCertificateInstallRetries {
 			detail := ""
 			if update.Detail != nil {
 				detail = *update.Detail
 			}
 			if err := svc.ds.RetryHostCertificateTemplate(ctx, host.UUID, update.CertificateTemplateID, detail); err != nil {
 				return ctxerr.Wrap(ctx, err, "retrying certificate install")
 			}
 			return nil
 		}
 	}
 
-	return svc.ds.UpsertCertificateStatus(ctx, update)
+	if err := svc.ds.UpsertCertificateStatus(ctx, update); err != nil {
+		return err
+	}
+
+	if update.OperationType == fleet.MDMOperationTypeInstall {
+		var actStatus fleet.CertificateActivityStatus
+		switch update.Status {
+		case fleet.MDMDeliveryVerified:
+			actStatus = fleet.CertificateActivityInstalled
+		case fleet.MDMDeliveryFailed:
+			actStatus = fleet.CertificateActivityFailedInstall
+		}
+		if actStatus != "" {
+			detail := ""
+			if update.Detail != nil {
+				detail = *update.Detail
+			}
+			if err := svc.NewActivity(ctx, nil, fleet.ActivityTypeInstalledCertificate{
+				HostID:                host.ID,
+				HostDisplayName:       host.DisplayName(),
+				CertificateTemplateID: update.CertificateTemplateID,
+				CertificateName:       record.Name,
+				Status:                string(actStatus),
+				Detail:                detail,
+			}); err != nil {
+				svc.logger.ErrorContext(ctx, "failed to create certificate install activity", "host.id", host.ID, "activity.status", actStatus, "err", err)
+				ctxerr.Handle(ctx, err)
+			}
+		}
+	}
+
+	return nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/service/certificates.go` around lines 699 - 744, The activity
(ActivityTypeInstalledCertificate with statuses
CertificateActivityInstalled/CertificateActivityFailedInstall) is created before
the datastore changes (RetryHostCertificateTemplate or UpsertCertificateStatus)
are persisted, which can leave activities that never correspond to a committed
state; change the flow so you first perform the datastore operation (for
MDMDeliveryFailed: call RetryHostCertificateTemplate when retrying, otherwise
call UpsertCertificateStatus to persist the final status), check for errors, and
only after the datastore call succeeds call svc.NewActivity to record the
CertificateActivity; preserve the same fields (detail from update.Detail,
host.ID/DisplayName, update.CertificateTemplateID, record.Name) when creating
the activity and keep existing error handling around NewActivity.
🧹 Nitpick comments (1)
server/fleet/activities.go (1)

1831-1837: Use typed status instead of raw string for install-certificate activity.

Line 1836 currently uses string, which bypasses the status type constraints added in this PR and can allow invalid values.

Proposed refactor
 type ActivityTypeInstalledCertificate struct {
 	HostID                uint   `json:"host_id"`
 	HostDisplayName       string `json:"host_display_name"`
 	CertificateTemplateID uint   `json:"certificate_template_id"`
 	CertificateName       string `json:"certificate_name"`
-	Status                string `json:"status"`
+	Status                CertificateActivityStatus `json:"status"`
 	Detail                string `json:"detail,omitempty"`
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/fleet/activities.go` around lines 1831 - 1837, The
ActivityTypeInstalledCertificate struct's Status field should use the new typed
status instead of raw string: change the Status field type in
ActivityTypeInstalledCertificate from string to the project's
certificate/install status type (e.g., InstallCertificateStatus or
CertificateInstallStatus introduced in this PR), update its JSON tag as needed,
and fix any call sites that assign or compare Status to use the typed enum
(including conversions or imports) so only valid status values are allowed.
🤖 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/schema.sql`:
- Around line 1811-1813: Remove the manually added migration_status_tables row
with value 20260331000000 (the tuple starting with (504,20260331000000,...)) and
any manual schema changes for the retry_count column from schema.sql, then
create a proper migration file named with the timestamp prefix 20260331000000
(i.e. 20260331000000*.go) under the migrations/tables migrations set that
performs the retry_count change; after adding that migration, run the project's
migration/schema-generation step so schema.sql is regenerated from migrations
and includes the new migration entry instead of a manual edit.

---

Duplicate comments:
In `@server/service/certificates.go`:
- Around line 699-744: The activity (ActivityTypeInstalledCertificate with
statuses CertificateActivityInstalled/CertificateActivityFailedInstall) is
created before the datastore changes (RetryHostCertificateTemplate or
UpsertCertificateStatus) are persisted, which can leave activities that never
correspond to a committed state; change the flow so you first perform the
datastore operation (for MDMDeliveryFailed: call RetryHostCertificateTemplate
when retrying, otherwise call UpsertCertificateStatus to persist the final
status), check for errors, and only after the datastore call succeeds call
svc.NewActivity to record the CertificateActivity; preserve the same fields
(detail from update.Detail, host.ID/DisplayName, update.CertificateTemplateID,
record.Name) when creating the activity and keep existing error handling around
NewActivity.

---

Nitpick comments:
In `@server/fleet/activities.go`:
- Around line 1831-1837: The ActivityTypeInstalledCertificate struct's Status
field should use the new typed status instead of raw string: change the Status
field type in ActivityTypeInstalledCertificate from string to the project's
certificate/install status type (e.g., InstallCertificateStatus or
CertificateInstallStatus introduced in this PR), update its JSON tag as needed,
and fix any call sites that assign or compare Status to use the typed enum
(including conversions or imports) so only valid status values are allowed.
🪄 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: 59c8ee8f-676b-43d1-b6fa-f4e697e1ab55

📥 Commits

Reviewing files that changed from the base of the PR and between 99a0413 and 7fcaa6b.

📒 Files selected for processing (13)
  • changes/37546-android-certificate-install-activity
  • server/datastore/mysql/certificate_templates.go
  • server/datastore/mysql/host_certificate_templates.go
  • server/datastore/mysql/host_certificate_templates_test.go
  • server/datastore/mysql/migrations/tables/20260331000000_AddCertRetryCount.go
  • server/datastore/mysql/schema.sql
  • server/fleet/activities.go
  • server/fleet/certificate_templates.go
  • server/fleet/datastore.go
  • server/fleet/host_certificate_template.go
  • server/mock/datastore_mock.go
  • server/service/certificates.go
  • server/service/integration_android_certificate_templates_test.go

Comment thread server/datastore/mysql/schema.sql
…ert-retry

# Conflicts:
#	changes/37546-android-certificate-install-activity
#	server/service/certificates.go
…cert-retry

# Conflicts:
#	changes/37546-android-certificate-install-activity
#	server/service/certificates.go
@getvictor getvictor marked this pull request as ready for review March 31, 2026 22:05
@getvictor getvictor requested a review from a team as a code owner March 31, 2026 22:05
Copy link
Copy Markdown

@claude claude Bot left a comment

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.

@getvictor getvictor merged commit f8e5a5d into main Apr 1, 2026
51 checks passed
@getvictor getvictor deleted the victor/37546-android-cert-retry branch April 1, 2026 18:49
kamushadenes pushed a commit to kamushadenes/fleet that referenced this pull request Apr 1, 2026
<!-- Add the related story/sub-task/bug number, like Resolves fleetdm#123, or
remove if NA -->
**Related issue:** Resolves fleetdm#37546

Docs: fleetdm#42780
Demo: https://www.youtube.com/watch?v=K44wRg9_79M

# Checklist for submitter

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

- [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.

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

## Database migrations

- [x] Checked schema for all modified table for columns that will
auto-update timestamps during migration.

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

* **New Features**
* Automatic retry for Android certificate installations: failed installs
are retried up to 3 times before marked terminal.
* Installation activities recorded: install/failed-install events (with
details) are logged for better visibility and troubleshooting.
* Resend/reset actions now reset retry state so retries behave
predictably after manual resend.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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.

Android certificates: Retry 3 times

3 participants