From efa8775ea71fe93e8ea49518b971c1d8f56e8288 Mon Sep 17 00:00:00 2001
From: Nico <32375741+nulmete@users.noreply.github.com>
Date: Thu, 6 Aug 2026 12:14:42 -0300
Subject: [PATCH] Fix policy automations table dropping rows for multi-host
automation runs (#50684)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
**Related issue:** Resolves #50683
The policy details page's Automation runs table showed the correct run
count but rendered only one row when a single automation run covered
multiple hosts (e.g. a failing-policies webhook batch): rows are
(activity, host) pairs, so batch rows share an activity id, and the
table's default row.id keying collapsed the duplicates.
# 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.
## Testing
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
#### Before
#### After
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed policy automation activity tables so activities with the same ID
are displayed as separate rows for each host.
* Corrected total run counts shown for host-specific activity rows.
---
...olicy-automations-table-multi-host-rows.md | 1 +
...PolicyAutomationsActivitiesTable.tests.tsx | 34 +++++++++++++++++++
.../PolicyAutomationsActivitiesTable.tsx | 7 ++++
3 files changed, 42 insertions(+)
create mode 100644 changes/50683-policy-automations-table-multi-host-rows.md
diff --git a/changes/50683-policy-automations-table-multi-host-rows.md b/changes/50683-policy-automations-table-multi-host-rows.md
new file mode 100644
index 0000000000..dfa2b52ce5
--- /dev/null
+++ b/changes/50683-policy-automations-table-multi-host-rows.md
@@ -0,0 +1 @@
+- Fixed the policy automations table showing only one host for automation runs that cover multiple hosts.
diff --git a/frontend/pages/policies/details/components/PolicyAutomationsActivitiesTable/PolicyAutomationsActivitiesTable.tests.tsx b/frontend/pages/policies/details/components/PolicyAutomationsActivitiesTable/PolicyAutomationsActivitiesTable.tests.tsx
index 847819adbe..a2ffbd1c89 100644
--- a/frontend/pages/policies/details/components/PolicyAutomationsActivitiesTable/PolicyAutomationsActivitiesTable.tests.tsx
+++ b/frontend/pages/policies/details/components/PolicyAutomationsActivitiesTable/PolicyAutomationsActivitiesTable.tests.tsx
@@ -155,6 +155,40 @@ describe("PolicyAutomationsActivitiesTable", () => {
expect(screen.getByPlaceholderText("Search hosts")).toBeInTheDocument();
});
+ it("renders one row per host for a batch activity sharing an activity id", async () => {
+ (policiesAPI.getAutomationActivities as jest.Mock).mockResolvedValue(
+ mockResponse([
+ mockActivity({
+ id: 41,
+ type: ActivityType.RanAutomationWebhook,
+ details: { policy_id: 123 },
+ host_id: 1,
+ host_display_name: "batch-host-a",
+ }),
+ mockActivity({
+ id: 41,
+ type: ActivityType.RanAutomationWebhook,
+ details: { policy_id: 123 },
+ host_id: 2,
+ host_display_name: "batch-host-b",
+ }),
+ ])
+ );
+
+ render(
+
+ );
+
+ // Both (activity, host) rows must render even though they share id 41.
+ expect(await screen.findByText("batch-host-a")).toBeInTheDocument();
+ expect(screen.getByText("batch-host-b")).toBeInTheDocument();
+ expect(screen.getByText("2 runs")).toBeInTheDocument();
+ });
+
it("shows the Reset policy button only when allowed", async () => {
(policiesAPI.getAutomationActivities as jest.Mock).mockResolvedValue(
mockResponse([mockActivity()], 1)
diff --git a/frontend/pages/policies/details/components/PolicyAutomationsActivitiesTable/PolicyAutomationsActivitiesTable.tsx b/frontend/pages/policies/details/components/PolicyAutomationsActivitiesTable/PolicyAutomationsActivitiesTable.tsx
index f25b378a12..f99faa956e 100644
--- a/frontend/pages/policies/details/components/PolicyAutomationsActivitiesTable/PolicyAutomationsActivitiesTable.tsx
+++ b/frontend/pages/policies/details/components/PolicyAutomationsActivitiesTable/PolicyAutomationsActivitiesTable.tsx
@@ -259,6 +259,13 @@ const PolicyAutomationsActivitiesTable = ({
+ `${row.id}-${row.host_id}`
+ }
isLoading={isLoading}
manualSortBy
pageIndex={page}