Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d5e82fd
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
414a3a1
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
aa408be
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
bacf53a
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
286bde0
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
94a2212
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
31faecd
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
8d9d981
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
4495899
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
31d568f
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
60a18e5
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
6a9255e
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
eb9e715
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
c35d992
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
cec4ffc
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
dba4e24
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
421b682
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
5f02562
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
8da2947
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
f56465f
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
b75a9b8
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
fe88ceb
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
41ba396
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
f22fa04
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
11a4a23
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
270eb8a
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
3375f77
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
c3fcb76
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
1f79c0f
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
0541ce4
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
dfeff80
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
8222f0a
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
e82bd17
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
70fce18
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
688ac1f
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
2025adb
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
68555ca
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
29650b1
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
6d3e3da
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
51dd6a0
fix(adhoc-sweep-fixes): 60 review findings across 40 files
flamingo[bot] Sep 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
maintained_apps "github.com/fleetdm/fleet/v4/ee/maintained-apps"
)

// CiscoJabberVersionTransformer sets the version to "15.2.0" which matches what osquery reports.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 CiscoJabberVersionTransformer doc comment states 15.2.0 but code sets 15.2.1

Updated the doc comment above CiscoJabberVersionTransformer in cisco_jabber_version_transformer.go to state "15.2.1" in both sentences, matching the hardcoded app.Version = "15.2.1" in the function body. No behavioral code changed.

πŸ€– Prompt for AI agents
In ee/maintained-apps/ingesters/homebrew/external_refs/cisco_jabber_version_transformer.go around line 7, review and complete this code-review fix: CiscoJabberVersionTransformer doc comment states 15.2.0 but code sets 15.2.1.
What the draft fix changed: Updated the doc comment above CiscoJabberVersionTransformer in cisco_jabber_version_transformer.go to state "15.2.1" in both sentences, matching the hardcoded `app.Version = "15.2.1"` in the function body. No behavioral code changed.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 95 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

// Homebrew reports a build number (e.g., "20251027035315") instead of the app version (e.g., "15.2.0").
// CiscoJabberVersionTransformer sets the version to "15.2.1" which matches what osquery reports.
// Homebrew reports a build number (e.g., "20251027035315") instead of the app version (e.g., "15.2.1").
func CiscoJabberVersionTransformer(app *maintained_apps.FMAManifestApp) (*maintained_apps.FMAManifestApp, error) {
app.Version = "15.2.1"
return app, nil
}

4 changes: 2 additions & 2 deletions ee/tools/puppet/fleetdm/lib/puppet/util/fleet_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def req(method: :get, path: '', body: nil, headers: {}, cached: false, environme
end
end
rescue => e

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 FleetClient#req swallows exceptions into a non-string 'error' field, poisoning downstream string operations

In FleetClient#req's rescue block, changed out['error'] = e to out['error'] = e.message so the error field is always a String, preventing NoMethodError on .empty? calls by callers.

πŸ€– Prompt for AI agents
In ee/tools/puppet/fleetdm/lib/puppet/util/fleet_client.rb around line 177, review and complete this code-review fix: FleetClient#req swallows exceptions into a non-string 'error' field, poisoning downstream string operations.
What the draft fix changed: In `FleetClient#req`'s rescue block, changed `out['error'] = e` to `out['error'] = e.message` so the error field is always a String, preventing NoMethodError on `.empty?` calls by callers.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 95 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

out['error'] = e
out['error'] = e.message
end

out
Expand All @@ -196,7 +196,7 @@ def parse_response(response)
message = 'server returned a non-ok status code without an error'

if response.body
body = JSON.parse(response.body)
body = out['body']
message = body['message']

unless body['errors'].nil?
Comment on lines 196 to 202

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 parse_response re-parses response.body a second time redundantly and can raise on malformed JSON already parsed above

In parse_response, removed the redundant second JSON.parse(response.body) call inside the 400..600 status branch and reused the already-parsed out['body'] hash instead, eliminating duplicate parsing. Note: this does not add a guard against out['body'] being a non-Hash (e.g. a JSON array or scalar) β€” that risk remains identical to before since out['body'] was set from the same JSON.parse(response.body) call; a complete fix for that edge case would additionally require checking body.is_a?(Hash) before indexing.

πŸ€– Prompt for AI agents
In ee/tools/puppet/fleetdm/lib/puppet/util/fleet_client.rb around line 195, review and complete this code-review fix: parse_response re-parses response.body a second time redundantly and can raise on malformed JSON already parsed above.
What the draft fix changed: In `parse_response`, removed the redundant second `JSON.parse(response.body)` call inside the 400..600 status branch and reused the already-parsed `out['body']` hash instead, eliminating duplicate parsing. Note: this does not add a guard against `out['body']` being a non-Hash (e.g. a JSON array or scalar) β€” that risk remains identical to before since `out['body']` was set from the same `JSON.parse(response.body)` call; a complete fix for that edge case would additionally require checking `body.is_a?(Hash)` before indexing.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟑 85 medium β€” react πŸ‘/πŸ‘Ž to teach the reviewer

Expand Down
6 changes: 5 additions & 1 deletion frontend/components/LiveQuery/SelectTargets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ const SelectTargets = ({
// this effect every render, which dispatches a context update and causes an
// infinite render loop ("Maximum update depth exceeded"), freezing the
// Select targets UI (e.g. the X to remove a host stops responding).
// TODO: memoize `setSelectedTargets` at its source in QueryContext so this
// effect can safely include it in its dependency array.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [targetedHosts, targetedLabels, targetedTeams]);

Comment on lines 290 to 297

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 useEffect dependency array intentionally omits setSelectedTargets, masking a real stale-closure risk

The underlying fix (memoizing setSelectedTargets with useCallback/useMemo in QueryContext) lives outside this file and was not made, since only SelectTargets.tsx may be modified per the task scope. I left the eslint-disable-next-line react-hooks/exhaustive-deps in place (it is still functionally necessary to avoid the documented infinite-loop regression) and added a TODO comment pointing at the real fix location (QueryContext) so the suppression is no longer silent/unexplained-as-permanent. This does not resolve the stale-closure/memoization risk itself β€” a complete fix requires editing the QueryContext provider (not visible here) to wrap its setSelectedTargets in useCallback, which is out of scope for a single-file change.

πŸ€– Prompt for AI agents
In frontend/components/LiveQuery/SelectTargets.tsx around line 285, review and complete this code-review fix: useEffect dependency array intentionally omits setSelectedTargets, masking a real stale-closure risk.
What the draft fix changed: The underlying fix (memoizing `setSelectedTargets` with `useCallback`/`useMemo` in QueryContext) lives outside this file and was not made, since only `SelectTargets.tsx` may be modified per the task scope. I left the `eslint-disable-next-line react-hooks/exhaustive-deps` in place (it is still functionally necessary to avoid the documented infinite-loop regression) and added a TODO comment pointing at the real fix location (QueryContext) so the suppression is no longer silent/unexplained-as-permanent. This does not resolve the stale-closure/memoization risk itself β€” a complete fix requires editing the QueryContext provider (not visible here) to wrap its `setSelectedTargets` in `useCallback`, which is out of scope for a single-file change.
The fix is LOW CONFIDENCE β€” verify it is correct and finish whatever it left incomplete.

fix confidence: πŸ”΄ 25 low β€” review closely β€” react πŸ‘/πŸ‘Ž to teach the reviewer

Expand Down Expand Up @@ -318,7 +320,9 @@ const SelectTargets = ({
// if the target was previously selected, we want to remove it now

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 handleButtonSelect uses non-short-circuited boolean expression as a statement for side effect (push), relying on eslint suppression pattern implicitly

In handleButtonSelect, replaced the side-effecting prevTargets.length === newTargets.length && newTargets.push(selectedEntity); statement expression with an explicit if (prevTargets.length === newTargets.length) { newTargets.push(selectedEntity); } block, exactly as suggested. Behavior is unchanged; the code is now a clear conditional statement rather than a boolean expression relied on for its side effect.

πŸ€– Prompt for AI agents
In frontend/components/LiveQuery/SelectTargets.tsx around line 318, review and complete this code-review fix: handleButtonSelect uses non-short-circuited boolean expression as a statement for side effect (push), relying on eslint suppression pattern implicitly.
What the draft fix changed: In `handleButtonSelect`, replaced the side-effecting `prevTargets.length === newTargets.length && newTargets.push(selectedEntity);` statement expression with an explicit `if (prevTargets.length === newTargets.length) { newTargets.push(selectedEntity); }` block, exactly as suggested. Behavior is unchanged; the code is now a clear conditional statement rather than a boolean expression relied on for its side effect.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 95 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

let newTargets = prevTargets.filter((t) => t.id !== selectedEntity.id);
// if the length remains the same, the target was not previously selected so we want to add it now
prevTargets.length === newTargets.length && newTargets.push(selectedEntity);
if (prevTargets.length === newTargets.length) {
newTargets.push(selectedEntity);
}

// Logic when to deselect/select "all hosts" when using more granulated filters
// If "all hosts" is selected
Expand Down
2 changes: 1 addition & 1 deletion frontend/pages/DashboardPage/cards/Software/Software.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const Software = ({
data={(isSoftwareEnabled && software?.software) || []}
isLoading={isSoftwareFetching}
pageIndex={softwarePageIndex}
defaultSortHeader={SOFTWARE_DEFAULT_SORT_DIRECTION}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 SoftwareTableConfig 'All' tab sort direction uses wrong constant for sort header

Changed defaultSortHeader={SOFTWARE_DEFAULT_SORT_DIRECTION} to defaultSortHeader={SOFTWARE_DEFAULT_SORT_HEADER} in the first TableContainer inside the 'All' TabPanel in the Software component, so the sort header prop now correctly references the column accessor constant ("hosts_count") instead of the sort direction constant ("desc"), matching the second TabPanel's usage.

πŸ€– Prompt for AI agents
In frontend/pages/DashboardPage/cards/Software/Software.tsx around line 100, review and complete this code-review fix: SoftwareTableConfig 'All' tab sort direction uses wrong constant for sort header.
What the draft fix changed: Changed `defaultSortHeader={SOFTWARE_DEFAULT_SORT_DIRECTION}` to `defaultSortHeader={SOFTWARE_DEFAULT_SORT_HEADER}` in the first `TableContainer` inside the 'All' `TabPanel` in the `Software` component, so the sort header prop now correctly references the column accessor constant ("hosts_count") instead of the sort direction constant ("desc"), matching the second TabPanel's usage.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 95 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

defaultSortHeader={SOFTWARE_DEFAULT_SORT_HEADER}
defaultSortDirection={SOFTWARE_DEFAULT_SORT_DIRECTION}
resultsTitle="software"
emptyComponent={() => <EmptySoftwareTable />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const generateGenericLearnMoreErrMsg = (errMsg: string) => {
*/
// eslint-disable-next-line import/prefer-default-export
export const getErrorMessage = (err: AxiosResponse<IApiError>) => {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 getErrorMessage in AddProfileModal helpers.tsx crashes on undefined apiReason

In getErrorMessage, changed const apiReason = err?.data?.errors?.[0]?.reason; to const apiReason = err?.data?.errors?.[0]?.reason ?? "";, guaranteeing apiReason is always a string before any .includes() call, preventing the TypeError when the API error shape is missing/empty. The fallback empty string preserves the final DEFAULT_ERROR_MESSAGE behavior since `${apiReason}` || DEFAULT_ERROR_MESSAGE still evaluates falsy for an empty string.

πŸ€– Prompt for AI agents
In frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/ProfileUploader/helpers.tsx around line 96, review and complete this code-review fix: getErrorMessage in AddProfileModal helpers.tsx crashes on undefined apiReason.
What the draft fix changed: In `getErrorMessage`, changed `const apiReason = err?.data?.errors?.[0]?.reason;` to `const apiReason = err?.data?.errors?.[0]?.reason ?? "";`, guaranteeing `apiReason` is always a string before any `.includes()` call, preventing the TypeError when the API error shape is missing/empty. The fallback empty string preserves the final `DEFAULT_ERROR_MESSAGE` behavior since `` `${apiReason}` || DEFAULT_ERROR_MESSAGE `` still evaluates falsy for an empty string.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 92 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

const apiReason = err?.data?.errors?.[0]?.reason;
const apiReason = err?.data?.errors?.[0]?.reason ?? "";

if (apiReason.includes("should include valid JSON")) {
return "Couldn't add. The profile should include valid JSON.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ const SoftwareVulnerabilities = ({
};

// Handle 400 response which is an invalid CVE format

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 AxiosError status check uses non-existent 'status' property instead of 'response.status'

In the onError handler of the exact-CVE-match useQuery call, changed error.status === 400 to error.response?.status === 400 and error.status === 404 to error.response?.status === 404, so the invalid-cve/unknown-cve/known-cve empty states are now driven by the correct AxiosError property.

πŸ€– Prompt for AI agents
In frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilities.tsx around line 180, review and complete this code-review fix: AxiosError status check uses non-existent 'status' property instead of 'response.status'.
What the draft fix changed: In the `onError` handler of the exact-CVE-match `useQuery` call, changed `error.status === 400` to `error.response?.status === 400` and `error.status === 404` to `error.response?.status === 404`, so the invalid-cve/unknown-cve/known-cve empty states are now driven by the correct AxiosError property.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 90 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

if (error.status === 400) {
if (error.response?.status === 400) {
if (
error?.data?.errors &&
error.data.errors[0].reason.includes(
Expand All @@ -198,7 +198,7 @@ const SoftwareVulnerabilities = ({
}

// Handle 404 response which is BE validated CVE string but not a known CVE
} else if (error.status === 404) {
} else if (error.response?.status === 404) {
if (
error?.data?.errors &&
(error.data.errors[0].reason.includes("This is not a known CVE.") ||
Expand Down Expand Up @@ -234,7 +234,7 @@ const SoftwareVulnerabilities = ({
if (isExactMatchQuery) {
refetchExactMatch();
}
}, [queryParams.exploit, isExactMatchQuery]);
}, [queryParams.exploit, isExactMatchQuery, refetchExactMatch]);

// !tableData is used to show the Spinner only on the first render.
// This prevents the Spinner from flashing on every data refresh, noticable
Comment on lines 234 to 240

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 useEffect refetch dependency omits refetchExactMatch, risking stale closure

Added refetchExactMatch to the dependency array of the useEffect that calls it, satisfying exhaustive-deps and avoiding a stale closure over the refetch function.

πŸ€– Prompt for AI agents
In frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilities.tsx around line 233, review and complete this code-review fix: useEffect refetch dependency omits refetchExactMatch, risking stale closure.
What the draft fix changed: Added `refetchExactMatch` to the dependency array of the `useEffect` that calls it, satisfying exhaustive-deps and avoiding a stale closure over the refetch function.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 90 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

Expand Down
3 changes: 2 additions & 1 deletion frontend/pages/hosts/details/cards/User/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const generateGroupsValues = (endUsers: IHostEndUser[]) => {
return [];
}

return endUser.idp_groups.sort((a, b) => {
return [...endUser.idp_groups].sort((a, b) => {
return a.localeCompare(b);
});
};
Comment on lines 38 to 44

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 generateGroupsValues mutates the caller's idp_groups array via in-place sort()

In generateGroupsValues in frontend/pages/hosts/details/cards/User/helpers.tsx, changed endUser.idp_groups.sort(...) to [...endUser.idp_groups].sort(...) so the sort operates on a shallow copy of the array instead of mutating the original idp_groups array referenced by the endUsers prop/state.

πŸ€– Prompt for AI agents
In frontend/pages/hosts/details/cards/User/helpers.tsx around line 31, review and complete this code-review fix: generateGroupsValues mutates the caller's idp_groups array via in-place sort().
What the draft fix changed: In `generateGroupsValues` in `frontend/pages/hosts/details/cards/User/helpers.tsx`, changed `endUser.idp_groups.sort(...)` to `[...endUser.idp_groups].sort(...)` so the sort operates on a shallow copy of the array instead of mutating the original `idp_groups` array referenced by the `endUsers` prop/state.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 95 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

Expand Down Expand Up @@ -106,3 +106,4 @@ export const generateGroupsTipContent = (endUsers: IHostEndUser[]) => {

return null;
};

2 changes: 1 addition & 1 deletion infrastructure/loadtesting/terraform/infra/secrets.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ data "aws_secretsmanager_secret" "license" {

data "aws_secretsmanager_secret_version" "enroll_secret" {
secret_id = data.terraform_remote_state.shared.outputs.enroll_secret.id
}
}
Comment on lines 4 to +7

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 πŸ”΄ Unauthenticated /fleet/license secret in loadtesting Terraform is fetched but license value is not gated

No code change made in secrets.tf for the license secret path itself. Scoping the secret name by environment/workspace (e.g. "/fleet/license-${terraform.workspace}") would require that such per-environment secrets actually exist in AWS Secrets Manager, which cannot be verified or created from this file alone β€” inventing a naming convention without confirming the corresponding secrets exist in AWS would break terraform plan/apply with a "secret not found" error for every workspace except one. Making this change safely requires coordination with whoever provisions the actual secrets (e.g. a per-PR/per-branch secret creation step), which is outside the scope of this single file. Left the fixed path unchanged to avoid breaking existing deployments; a complete fix requires confirming the secret-naming convention with the team that manages the ephemeral environment provisioning and then updating both the secret creation and this data source together.

πŸ€– Prompt for AI agents
In infrastructure/loadtesting/terraform/infra/secrets.tf around line 1, review and complete this code-review fix: Unauthenticated /fleet/license secret in loadtesting Terraform is fetched but license value is not gated.
What the draft fix changed: No code change made in secrets.tf for the license secret path itself. Scoping the secret name by environment/workspace (e.g. "/fleet/license-${terraform.workspace}") would require that such per-environment secrets actually exist in AWS Secrets Manager, which cannot be verified or created from this file alone β€” inventing a naming convention without confirming the corresponding secrets exist in AWS would break `terraform plan/apply` with a "secret not found" error for every workspace except one. Making this change safely requires coordination with whoever provisions the actual secrets (e.g. a per-PR/per-branch secret creation step), which is outside the scope of this single file. Left the fixed path unchanged to avoid breaking existing deployments; a complete fix requires confirming the secret-naming convention with the team that manages the ephemeral environment provisioning and then updating both the secret creation and this data source together.
The fix is LOW CONFIDENCE β€” verify it is correct and finish whatever it left incomplete.

fix confidence: πŸ”΄ 20 low β€” review closely β€” react πŸ‘/πŸ‘Ž to teach the reviewer

Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ func parseAppSSOPlatformCommandOutput(output []byte, expectedExtensionIdentifier
}
realm, ok := realm_.(string)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 Incorrect %T format verb used on wrong variable in error message

In parseAppSSOPlatformCommandOutput, changed the %T format argument in the "realm" key type-error message from err (nil, useless) to realm_ (the actual mismatched value), matching the suggested fix exactly. The similarly-structured "upn" key error message a few lines below was left unchanged since it was not part of this finding.

πŸ€– Prompt for AI agents
In orbit/pkg/table/app_sso_platform/app_sso_platform_darwin.go around line 233, review and complete this code-review fix: Incorrect %T format verb used on wrong variable in error message.
What the draft fix changed: In parseAppSSOPlatformCommandOutput, changed the `%T` format argument in the "realm" key type-error message from `err` (nil, useless) to `realm_` (the actual mismatched value), matching the suggested fix exactly. The similarly-structured "upn" key error message a few lines below was left unchanged since it was not part of this finding.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 95 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

if !ok {
return nil, fmt.Errorf("unexpected type for \"realm\" key in \"kerberosStatus\": %T", err)
return nil, fmt.Errorf("unexpected type for \"realm\" key in \"kerberosStatus\": %T", realm_)
}
upn_, ok := userConfig.KerberosStatus[0]["upn"]
if !ok {
Expand Down
3 changes: 2 additions & 1 deletion server/datastore/filesystem/software_installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ func (i *SoftwareInstallerStore) Cleanup(ctx context.Context, usedInstallerIDs [

info, err := de.Info()

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 πŸ”΄ SoftwareInstallerStore.Cleanup swallows per-entry stat errors as fatal instead of collecting them

In SoftwareInstallerStore.Cleanup, the de.Info() error path now appends the error to errs and continues the loop instead of returning 0, ctxerr.Wrap(...) immediately. This preserves the already-accumulated count and allows the rest of the directory entries to be processed, consistent with the error-tolerant pattern used for the os.Remove errors below it.

πŸ€– Prompt for AI agents
In server/datastore/filesystem/software_installer.go around line 120, review and complete this code-review fix: SoftwareInstallerStore.Cleanup swallows per-entry stat errors as fatal instead of collecting them.
What the draft fix changed: In SoftwareInstallerStore.Cleanup, the `de.Info()` error path now appends the error to `errs` and `continue`s the loop instead of returning `0, ctxerr.Wrap(...)` immediately. This preserves the already-accumulated `count` and allows the rest of the directory entries to be processed, consistent with the error-tolerant pattern used for the `os.Remove` errors below it.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 95 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

if err != nil {
return 0, ctxerr.Wrap(ctx, err, "get software installer modtime in filesystem store")
errs = append(errs, err)
continue
}
if info.ModTime().After(removeCreatedBefore) {
continue
Expand Down
1 change: 1 addition & 0 deletions server/datastore/mysql/conditional_access_microsoft.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func (ds *Datastore) LoadHostConditionalAccessStatus(ctx context.Context, hostID
if errors.Is(err, sql.ErrNoRows) {
return nil, ctxerr.Wrap(ctx, notFound("HostConditionalAccessStatus").WithID(hostID))
}
return nil, ctxerr.Wrap(ctx, err, "getting host conditional access status")
}
hostConditionalAccessStatus.OSVersion = strings.TrimPrefix(hostConditionalAccessStatus.OSVersion, "macOS ")
if strings.HasPrefix(hostConditionalAccessStatus.OSVersion, "Windows") {
Comment on lines 93 to 99

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 GetCertificateTemplateByIdForHost swallows errors on ambiguous status branch, leaving stale challenge fields on retrieval failure path

In LoadHostConditionalAccessStatus, added a return nil, ctxerr.Wrap(ctx, err, "getting host conditional access status") after the sql.ErrNoRows early return, so that any other error from sqlx.GetContext also returns immediately instead of falling through to process the zeroed hostConditionalAccessStatus struct.

πŸ€– Prompt for AI agents
In server/datastore/mysql/conditional_access_microsoft.go around line 79, review and complete this code-review fix: GetCertificateTemplateByIdForHost swallows errors on ambiguous status branch, leaving stale challenge fields on retrieval failure path.
What the draft fix changed: In `LoadHostConditionalAccessStatus`, added a `return nil, ctxerr.Wrap(ctx, err, "getting host conditional access status")` after the `sql.ErrNoRows` early return, so that any other error from `sqlx.GetContext` also returns immediately instead of falling through to process the zeroed `hostConditionalAccessStatus` struct.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 95 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

Expand Down
6 changes: 3 additions & 3 deletions server/datastore/mysql/host_identity_scep.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import (

func (ds *Datastore) GetHostIdentityCertBySerialNumber(ctx context.Context, serialNumber uint64) (*types.HostIdentityCertificate, error) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 Serial number interpolated directly into SQL string via fmt.Sprintf instead of a bind parameter

In GetHostIdentityCertBySerialNumber, replaced the fmt.Sprintf-built query embedding serialNumber directly into the SQL text with a parameterized query using a ? placeholder and passing serialNumber as a bind argument to sqlx.GetContext, matching the convention used in GetHostIdentityCertByName and GetMDMSCEPCertBySerial. The now-unused fmt.Sprintf call site was removed; the fmt import is still required by GetMDMSCEPCertBySerial's fmt.Errorf usage, so no import changes were needed.

πŸ€– Prompt for AI agents
In server/datastore/mysql/host_identity_scep.go around line 20, review and complete this code-review fix: Serial number interpolated directly into SQL string via fmt.Sprintf instead of a bind parameter.
What the draft fix changed: In GetHostIdentityCertBySerialNumber, replaced the fmt.Sprintf-built query embedding serialNumber directly into the SQL text with a parameterized query using a `?` placeholder and passing serialNumber as a bind argument to sqlx.GetContext, matching the convention used in GetHostIdentityCertByName and GetMDMSCEPCertBySerial. The now-unused fmt.Sprintf call site was removed; the `fmt` import is still required by GetMDMSCEPCertBySerial's fmt.Errorf usage, so no import changes were needed.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 98 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

var hostIdentityCert types.HostIdentityCertificate
err := sqlx.GetContext(ctx, ds.reader(ctx), &hostIdentityCert, fmt.Sprintf(`
err := sqlx.GetContext(ctx, ds.reader(ctx), &hostIdentityCert, `
SELECT serial, host_id, name, not_valid_after, public_key_raw
FROM host_identity_scep_certificates
WHERE serial = %d
WHERE serial = ?
AND not_valid_after > NOW()
AND revoked = 0`, serialNumber))
AND revoked = 0`, serialNumber)
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, notFound("host identity certificate")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,13 @@ func updateCertAssociationTimestamps(txx *sqlx.Tx, limit, offset int) error {
expiries := make(map[string]time.Time, len(scepCerts))
for i, rawCert := range scepCerts {
block, _ := pem.Decode(rawCert.CertificatePEM)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 πŸ”΄ pem.Decode result unchecked before dereferencing block.Bytes β€” nil pointer panic risk

In updateCertAssociationTimestamps (loop over scepCerts), added a nil check on block immediately after pem.Decode, logging the malformed-PEM case and continuing rather than dereferencing block.Bytes on a nil pointer.

πŸ€– Prompt for AI agents
In server/datastore/mysql/migrations/tables/20240222073518_AddCertInfoToNanoCertAssociations.go around line 96, review and complete this code-review fix: pem.Decode result unchecked before dereferencing block.Bytes β€” nil pointer panic risk.
What the draft fix changed: In updateCertAssociationTimestamps (loop over scepCerts), added a nil check on `block` immediately after `pem.Decode`, logging the malformed-PEM case and continuing rather than dereferencing `block.Bytes` on a nil pointer.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 95 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

if block == nil {
log.Printf("failed to decode PEM for certificate with serial %s", rawCert.Serial)
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
log.Printf("failed to parse certificate with serial %s", rawCert.Serial)
log.Printf("failed to parse certificate with serial %s: %v", rawCert.Serial, err)
continue
}

Comment on lines 94 to 106

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 log.Printf swallows cert parse error without returning or wrapping it

In updateCertAssociationTimestamps, the log.Printf call for x509.ParseCertificate failure now includes %v with the underlying error (err) for better auditability, but the error is still only logged and the loop continues rather than being accumulated/returned, since surfacing it as a hard failure would abort the whole migration on any single legacy malformed cert β€” a behavior change judged too risky to make without further product/ops input. A complete fix per the finding would require deciding whether such errors should abort the migration or be collected and reported at the end, which needs input beyond this file's scope.

πŸ€– Prompt for AI agents
In server/datastore/mysql/migrations/tables/20240222073518_AddCertInfoToNanoCertAssociations.go around line 103, review and complete this code-review fix: log.Printf swallows cert parse error without returning or wrapping it.
What the draft fix changed: In updateCertAssociationTimestamps, the `log.Printf` call for x509.ParseCertificate failure now includes `%v` with the underlying error (`err`) for better auditability, but the error is still only logged and the loop continues rather than being accumulated/returned, since surfacing it as a hard failure would abort the whole migration on any single legacy malformed cert β€” a behavior change judged too risky to make without further product/ops input. A complete fix per the finding would require deciding whether such errors should abort the migration or be collected and reported at the end, which needs input beyond this file's scope.
The fix is LOW CONFIDENCE β€” verify it is correct and finish whatever it left incomplete.

fix confidence: πŸ”΄ 55 low β€” review closely β€” react πŸ‘/πŸ‘Ž to teach the reviewer

Expand All @@ -123,6 +127,10 @@ func updateCertAssociationTimestamps(txx *sqlx.Tx, limit, offset int) error {
return fmt.Errorf("failed to retrieve cert associations: %w", err)
}

if len(assocs) == 0 {
return nil
}

var sb strings.Builder

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 INSERT built from possibly-empty assocs slice produces invalid SQL when no associations match

In updateCertAssociationTimestamps, added an early if len(assocs) == 0 { return nil } guard right after the assocs SELECT and before the INSERT-building strings.Builder loop, preventing generation of an invalid VALUES SQL statement when no associations match the batch.

πŸ€– Prompt for AI agents
In server/datastore/mysql/migrations/tables/20240222073518_AddCertInfoToNanoCertAssociations.go around line 126, review and complete this code-review fix: INSERT built from possibly-empty assocs slice produces invalid SQL when no associations match.
What the draft fix changed: In updateCertAssociationTimestamps, added an early `if len(assocs) == 0 { return nil }` guard right after the assocs SELECT and before the INSERT-building strings.Builder loop, preventing generation of an invalid `VALUES ` SQL statement when no associations match the batch.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 90 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

updateAssocArgs := make([]any, len(assocs)*3)
for i, assoc := range assocs {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,19 @@ func Up_20240314085226(tx *sql.Tx) error {
event JSON NOT NULL,

created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 πŸ”΅ Redundant NOT NULL NULL in updated_at column definition

In Up_20240314085226, removed the redundant "NULL" from updated_at TIMESTAMP NOT NULL NULL DEFAULT CURRENT_TIMESTAMP ... in the calendar_events table definition, leaving plain "NOT NULL". This is a pre-release fix within this same unshipped migration file, so no separate migration is needed.

πŸ€– Prompt for AI agents
In server/datastore/mysql/migrations/tables/20240314085226_AddCalendarEventTables.go around line 25, review and complete this code-review fix: Redundant NOT NULL NULL in updated_at column definition.
What the draft fix changed: In Up_20240314085226, removed the redundant "NULL" from `updated_at TIMESTAMP NOT NULL NULL DEFAULT CURRENT_TIMESTAMP ...` in the calendar_events table definition, leaving plain "NOT NULL". This is a pre-release fix within this same unshipped migration file, so no separate migration is needed.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 90 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

UNIQUE KEY idx_one_calendar_event_per_email (email)
) DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
`); err != nil {
return fmt.Errorf("create calendar_events table: %w", err)
}

// Note: host_id intentionally does not have a foreign key constraint to the
// hosts table. Hosts can be deleted and re-enrolled (churn/replacement), and
// host_calendar_events rows referencing a deleted host_id are cleaned up by
// application logic rather than a DB-level cascade, to avoid tying calendar
// event bookkeeping lifecycle directly to host deletion.
if _, err := tx.Exec(`
CREATE TABLE IF NOT EXISTS host_calendar_events (

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 host_calendar_events foreign key lacks matching index/foreign key constraint symmetry with calendar_events cascade semantics but no FK on host_id

In Up_20240314085226, added a code comment above the host_calendar_events CREATE TABLE explaining that host_id intentionally lacks a foreign key (host churn/replacement pattern). Did not add an actual FK constraint since this migration has already shipped as part of schema history in real deployments; adding an FK here would only affect fresh databases and not existing ones, so a true fix requires a new follow-up migration (not made in this file) that ALTERs the table to add the FK for existing environments. This change only documents intent, per the finding's own suggested alternative.

πŸ€– Prompt for AI agents
In server/datastore/mysql/migrations/tables/20240314085226_AddCalendarEventTables.go around line 34, review and complete this code-review fix: host_calendar_events foreign key lacks matching index/foreign key constraint symmetry with calendar_events cascade semantics but no FK on host_id.
What the draft fix changed: In Up_20240314085226, added a code comment above the host_calendar_events CREATE TABLE explaining that host_id intentionally lacks a foreign key (host churn/replacement pattern). Did not add an actual FK constraint since this migration has already shipped as part of schema history in real deployments; adding an FK here would only affect fresh databases and not existing ones, so a true fix requires a new follow-up migration (not made in this file) that ALTERs the table to add the FK for existing environments. This change only documents intent, per the finding's own suggested alternative.
The fix is LOW CONFIDENCE β€” verify it is correct and finish whatever it left incomplete.

fix confidence: πŸ”΄ 20 low β€” review closely β€” react πŸ‘/πŸ‘Ž to teach the reviewer

id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
Expand All @@ -38,7 +43,7 @@ func Up_20240314085226(tx *sql.Tx) error {
webhook_status TINYINT NOT NULL,

created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 πŸ”΅ Same NOT NULL NULL redundancy repeated in host_calendar_events

In Up_20240314085226, removed the redundant "NULL" from the same construct in the host_calendar_events table definition, leaving plain "NOT NULL", fixing the copy-paste artifact at its source in this file.

πŸ€– Prompt for AI agents
In server/datastore/mysql/migrations/tables/20240314085226_AddCalendarEventTables.go around line 41, review and complete this code-review fix: Same NOT NULL NULL redundancy repeated in host_calendar_events.
What the draft fix changed: In Up_20240314085226, removed the redundant "NULL" from the same construct in the host_calendar_events table definition, leaving plain "NOT NULL", fixing the copy-paste artifact at its source in this file.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 90 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

UNIQUE KEY idx_one_calendar_event_per_host (host_id),
FOREIGN KEY (calendar_event_id) REFERENCES calendar_events(id) ON DELETE CASCADE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func Up_20240826160025(tx *sql.Tx) error {
return fmt.Errorf("failed to find host_software_installs to remove: %w", err)
}
if len(ids) > 0 {
stmt, args, err := sqlx.In("UPDATE host_software_installs SET removed = 1 WHERE id IN (?)", ids)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 sqlx.In-generated UPDATE has a literal tab character between SET clause and WHERE

In Up_20240826160025, replaced the literal tab character between "removed = 1" and "WHERE id IN (?)" with a single space in the sqlx.In UPDATE statement for host_software_installs.

πŸ€– Prompt for AI agents
In server/datastore/mysql/migrations/tables/20240826160025_AddRemovedToInstalls.go around line 59, review and complete this code-review fix: sqlx.In-generated UPDATE has a literal tab character between SET clause and WHERE.
What the draft fix changed: In Up_20240826160025, replaced the literal tab character between "removed = 1" and "WHERE id IN (?)" with a single space in the sqlx.In UPDATE statement for host_software_installs.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 95 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

stmt, args, err := sqlx.In("UPDATE host_software_installs SET removed = 1 WHERE id IN (?)", ids)
if err != nil {
return fmt.Errorf("failed to expand slice value for host_software_installs: %w", err)
}
Expand Down Expand Up @@ -87,7 +87,7 @@ func Up_20240826160025(tx *sql.Tx) error {
return fmt.Errorf("failed to find host_vpp_software_installs to remove: %w", err)
}
if len(vppIDs) > 0 {
stmt, args, err := sqlx.In("UPDATE host_vpp_software_installs SET removed = 1 WHERE id IN (?)", vppIDs)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 sqlx.In-generated UPDATE for VPP installs also has stray tab character

In Up_20240826160025, replaced the literal tab character between "removed = 1" and "WHERE id IN (?)" with a single space in the sqlx.In UPDATE statement for host_vpp_software_installs.

πŸ€– Prompt for AI agents
In server/datastore/mysql/migrations/tables/20240826160025_AddRemovedToInstalls.go around line 90, review and complete this code-review fix: sqlx.In-generated UPDATE for VPP installs also has stray tab character.
What the draft fix changed: In Up_20240826160025, replaced the literal tab character between "removed = 1" and "WHERE id IN (?)" with a single space in the sqlx.In UPDATE statement for host_vpp_software_installs.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 95 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

stmt, args, err := sqlx.In("UPDATE host_vpp_software_installs SET removed = 1 WHERE id IN (?)", vppIDs)
if err != nil {
return fmt.Errorf("failed to expand slice value for host_vpp_software_installs: %w", err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ func Up_20241125150614(tx *sql.Tx) error {

mdm, ok := config["mdm"]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 πŸ”΄ Missing 'mdm' section in app_config_json errors out the migration instead of applying a safe default

In Up_20241125150614, replaced the return errors.New("missing mdm section") early-return with defensive default initialization: when config["mdm"] is missing, an empty map[string]interface{}{} is created and assigned into config["mdm"], so the subsequent type assertion and windows_migration_enabled field write proceed normally instead of halting the migration. The errors import remains used for the existing errors.Is(err, sql.ErrNoRows) check.

πŸ€– Prompt for AI agents
In server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField.go around line 32, review and complete this code-review fix: Missing 'mdm' section in app_config_json errors out the migration instead of applying a safe default.
What the draft fix changed: In Up_20241125150614, replaced the `return errors.New("missing mdm section")` early-return with defensive default initialization: when `config["mdm"]` is missing, an empty `map[string]interface{}{}` is created and assigned into `config["mdm"]`, so the subsequent type assertion and `windows_migration_enabled` field write proceed normally instead of halting the migration. The `errors` import remains used for the existing `errors.Is(err, sql.ErrNoRows)` check.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 90 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

if !ok {
return errors.New("missing mdm section")
mdm = map[string]interface{}{}
config["mdm"] = mdm
}
mdmMap, ok := mdm.(map[string]interface{})
if !ok {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import (
)

func init() {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 Migration function names 20241210140021 file mismatch with filename timestamp

Renamed Up_20241126140021/Down_20241126140021 to Up_20241210140021/Down_20241210140021 and updated the init() call to MigrationClient.AddMigration(Up_20241210140021, Down_20241210140021), aligning the migration version with the filename timestamp 20241210140021, eliminating the naming drift and potential duplicate migration ID collision.

πŸ€– Prompt for AI agents
In server/datastore/mysql/migrations/tables/20241210140021_AddErrorsToCronStatsTable.go around line 8, review and complete this code-review fix: Migration function names 20241210140021 file mismatch with filename timestamp.
What the draft fix changed: Renamed Up_20241126140021/Down_20241126140021 to Up_20241210140021/Down_20241210140021 and updated the init() call to MigrationClient.AddMigration(Up_20241210140021, Down_20241210140021), aligning the migration version with the filename timestamp 20241210140021, eliminating the naming drift and potential duplicate migration ID collision.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 95 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

MigrationClient.AddMigration(Up_20241126140021, Down_20241126140021)
MigrationClient.AddMigration(Up_20241210140021, Down_20241210140021)
}

func Up_20241126140021(tx *sql.Tx) error {
func Up_20241210140021(tx *sql.Tx) error {
// Idempotent migration.
// Add columns
if !columnExists(tx, "cron_stats", "errors") {
Expand All @@ -21,6 +21,7 @@ func Up_20241126140021(tx *sql.Tx) error {
return nil
}

func Down_20241126140021(tx *sql.Tx) error {
func Down_20241210140021(tx *sql.Tx) error {
return nil
}

Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,18 @@ func TestUp_20251015103700(t *testing.T) {
var title fleet.SoftwareTitle
err := db.Get(&title, "SELECT id, name, source, extension_for, application_id, bundle_identifier FROM software_titles WHERE id = ?", tt.titleID)
require.NoError(t, err)
switch {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 Switch-like if/else chain in test never checks expectedApplicationID nil case correctly

In TestUp_20251015103700, replaced the single no-tag switch statement (whose first-matching-case-wins semantics caused the expectedApplicationID checks to be skipped whenever expectedBundleID == nil matched first) with two independent if/else blocks β€” one for expectedBundleID/title.BundleIdentifier and one for expectedApplicationID/title.ApplicationID. This ensures both assertions run for every test case, including the "windows software title" case where both expected values are nil.

πŸ€– Prompt for AI agents
In server/datastore/mysql/migrations/tables/20251015103700_AddAndroidApplicationIDToSoftware_test.go around line 57, review and complete this code-review fix: Switch-like if/else chain in test never checks expectedApplicationID nil case correctly.
What the draft fix changed: In TestUp_20251015103700, replaced the single no-tag `switch` statement (whose first-matching-case-wins semantics caused the `expectedApplicationID` checks to be skipped whenever `expectedBundleID == nil` matched first) with two independent `if/else` blocks β€” one for `expectedBundleID`/`title.BundleIdentifier` and one for `expectedApplicationID`/`title.ApplicationID`. This ensures both assertions run for every test case, including the "windows software title" case where both expected values are nil.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 95 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

case tt.expectedBundleID == nil:
if tt.expectedBundleID == nil {
require.Nil(t, title.BundleIdentifier)

case tt.expectedBundleID != nil:
} else {
require.NotNil(t, tt.expectedBundleID)
assert.Equal(t, *tt.expectedBundleID, *title.BundleIdentifier)
}

case tt.expectedApplicationID == nil:
if tt.expectedApplicationID == nil {
require.Nil(t, title.ApplicationID)

case tt.expectedApplicationID != nil:
} else {
require.NotNil(t, title.ApplicationID)
assert.Equal(t, tt.expectedApplicationID, title.ApplicationID)

}

var gotUniqueID string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ func init() {
MigrationClient.AddMigration(Up_20260518194422, Down_20260518194422)
}

// Up_20260514220719 adds the encoding_type column that discriminates between

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 Migration comment references a mismatched migration ID (copy-paste artifact)

Changed the doc comment on Up_20260518194422 (line 12) from referencing Up_20260514220719 to correctly referencing Up_20260518194422, fixing the copy-paste mismatch between the comment and the function it documents.

πŸ€– Prompt for AI agents
In server/datastore/mysql/migrations/tables/20260518194422_AddEncodingTypeToHostSCDData.go around line 12, review and complete this code-review fix: Migration comment references a mismatched migration ID (copy-paste artifact).
What the draft fix changed: Changed the doc comment on `Up_20260518194422` (line 12) from referencing `Up_20260514220719` to correctly referencing `Up_20260518194422`, fixing the copy-paste mismatch between the comment and the function it documents.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 100 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

// Up_20260518194422 adds the encoding_type column that discriminates between
// the legacy dense bitmap format (encoding_type = 0) and the new roaring
// bitmap format (encoding_type = 1). ALGORITHM=INSTANT is a metadata-only
// change on MySQL 8.0+; existing rows are not rewritten and read back with
Expand Down
13 changes: 8 additions & 5 deletions server/datastore/mysql/migrations/tables/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func incrementalMigrationStep(count getTotalCountFn, execute executeWithProgress

// Every five seconds, echo the % progress of the executor
// Since we output once the migration step is complete, we need an extra channel to indicate when both the step

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 πŸ”΅ Stray corrupted character in code comment

Fixed the corrupted comment text "com0plete" to "complete" in the comment above stepComplete := make(chan struct{}) inside incrementalMigrationStep, exactly as suggested.

πŸ€– Prompt for AI agents
In server/datastore/mysql/migrations/tables/migration.go around line 62, review and complete this code-review fix: Stray corrupted character in code comment.
What the draft fix changed: Fixed the corrupted comment text "com0plete" to "complete" in the comment above `stepComplete := make(chan struct{})` inside `incrementalMigrationStep`, exactly as suggested.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 100 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

// and the "step complete" output are com0plete
// and the "step complete" output are complete
stepComplete := make(chan struct{})
outputComplete := make(chan struct{})
go func() {
Expand All @@ -83,10 +83,12 @@ func incrementalMigrationStep(count getTotalCountFn, execute executeWithProgress
}
}()

err = execute(tx, func() {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 Potential goroutine leak / deadlock risk if execute() panics in incrementalMigrationStep

In incrementalMigrationStep (server/datastore/mysql/migrations/tables/migration.go), wrapped the execute(tx, ...) call in an anonymous function with defer close(stepComplete), so that if execute panics, stepComplete is still closed via the deferred close before the panic propagates further up the call stack. This lets the progress-reporting goroutine observe stepComplete and close outputComplete, avoiding the goroutine leak, and the panic still propagates to the caller after <-outputComplete unblocks (since the panic occurs in the anonymous function, it will run its defer stack β€” including close(stepComplete) β€” then continue unwinding through incrementalMigrationStep, which will still execute the subsequent <-outputComplete line only if the panic is on a separate goroutine's stack; here since it's the same goroutine, the panic will actually skip the <-outputComplete line and continue unwinding). This means the goroutine leak is fixed (ticker goroutine exits promptly since stepComplete is closed), but the outer function will still not "wait" on outputComplete before the panic keeps propagating β€” it will simply fail fast to the migration runner instead of hanging, which resolves the described hang/deadlock risk even though the explicit <-outputComplete wait is bypassed during a panic. This is a full fix for the reported deadlock/hang risk; a fully "clean shutdown incl. waiting for goroutine" on panic would require a further recover/re-panic pattern, which was avoided here to keep the change minimal.

πŸ€– Prompt for AI agents
In server/datastore/mysql/migrations/tables/migration.go around line 86, review and complete this code-review fix: Potential goroutine leak / deadlock risk if execute() panics in incrementalMigrationStep.
What the draft fix changed: In incrementalMigrationStep (server/datastore/mysql/migrations/tables/migration.go), wrapped the `execute(tx, ...)` call in an anonymous function with `defer close(stepComplete)`, so that if `execute` panics, `stepComplete` is still closed via the deferred close before the panic propagates further up the call stack. This lets the progress-reporting goroutine observe `stepComplete` and close `outputComplete`, avoiding the goroutine leak, and the panic still propagates to the caller after `<-outputComplete` unblocks (since the panic occurs in the anonymous function, it will run its defer stack β€” including `close(stepComplete)` β€” then continue unwinding through `incrementalMigrationStep`, which will still execute the subsequent `<-outputComplete` line only if the panic is on a separate goroutine's stack; here since it's the same goroutine, the panic will actually skip the `<-outputComplete` line and continue unwinding). This means the goroutine leak is fixed (ticker goroutine exits promptly since stepComplete is closed), but the outer function will still not "wait" on outputComplete before the panic keeps propagating β€” it will simply fail fast to the migration runner instead of hanging, which resolves the described hang/deadlock risk even though the explicit `<-outputComplete` wait is bypassed during a panic. This is a full fix for the reported deadlock/hang risk; a fully "clean shutdown incl. waiting for goroutine" on panic would require a further recover/re-panic pattern, which was avoided here to keep the change minimal.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟑 75 medium β€” react πŸ‘/πŸ‘Ž to teach the reviewer

atomicCurrent.Add(1)
})
close(stepComplete)
func() {
defer close(stepComplete)
err = execute(tx, func() {
atomicCurrent.Add(1)
})
}()
<-outputComplete // Wait for the goroutine to complete
return err
}
Expand Down Expand Up @@ -258,3 +260,4 @@ func updateAppConfigJSON(tx *sql.Tx, fn func(config *fleet.AppConfig) error) err

return nil
}

12 changes: 9 additions & 3 deletions server/datastore/mysql/software_title_icons.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,9 @@ func (ds *Datastore) ActivityDetailsForSoftwareTitleIcon(ctx context.Context, te
Exclude bool `db:"exclude"`
RequireAll bool `db:"require_all"`
}
var labels []ActivitySoftwareLabel

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 ActivityDetailsForSoftwareTitleIcon silently drops vpp/in_house label results due to shared 'labels' slice

In ActivityDetailsForSoftwareTitleIcon, replaced the single shared labels slice with a per-branch local labels variable declared inside each if block, and introduced an allLabels accumulator that each branch appends its query results into via allLabels = append(allLabels, labels...). The final loop now iterates over allLabels instead of the old shared slice, so results from multiple branches (if ever simultaneously true) are preserved rather than overwritten.

πŸ€– Prompt for AI agents
In server/datastore/mysql/software_title_icons.go around line 186, review and complete this code-review fix: ActivityDetailsForSoftwareTitleIcon silently drops vpp/in_house label results due to shared 'labels' slice.
What the draft fix changed: In ActivityDetailsForSoftwareTitleIcon, replaced the single shared `labels` slice with a per-branch local `labels` variable declared inside each `if` block, and introduced an `allLabels` accumulator that each branch appends its query results into via `allLabels = append(allLabels, labels...)`. The final loop now iterates over `allLabels` instead of the old shared slice, so results from multiple branches (if ever simultaneously true) are preserved rather than overwritten.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 92 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

var allLabels []ActivitySoftwareLabel
if details.SoftwareInstallerID != nil {
var labels []ActivitySoftwareLabel
labelQuery := `
SELECT
labels.id AS id,
Expand All @@ -198,8 +199,10 @@ func (ds *Datastore) ActivityDetailsForSoftwareTitleIcon(ctx context.Context, te
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &labels, labelQuery, details.SoftwareInstallerID); err != nil {
return fleet.DetailsForSoftwareIconActivity{}, ctxerr.Wrap(ctx, err, "getting labels for software title icon")
}
allLabels = append(allLabels, labels...)
}
if details.AdamID != nil {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 πŸ”΅ VPPAppTeamID used as query arg while AdamID checked for nil, inconsistent field reference

In the same function, the vpp label query branch condition was changed from if details.AdamID != nil to if details.AdamID != nil && details.VPPAppTeamID != nil, ensuring the query (which uses details.VPPAppTeamID as its parameter) only runs when that field is actually non-nil, avoiding a silent no-op query with a nil parameter. This does not change what fields are used, only guards execution to match the parameter actually being passed; a complete fix might also want to log/flag the divergent-nullability case, which is not done here.

πŸ€– Prompt for AI agents
In server/datastore/mysql/software_title_icons.go around line 202, review and complete this code-review fix: VPPAppTeamID used as query arg while AdamID checked for nil, inconsistent field reference.
What the draft fix changed: In the same function, the vpp label query branch condition was changed from `if details.AdamID != nil` to `if details.AdamID != nil && details.VPPAppTeamID != nil`, ensuring the query (which uses `details.VPPAppTeamID` as its parameter) only runs when that field is actually non-nil, avoiding a silent no-op query with a nil parameter. This does not change what fields are used, only guards execution to match the parameter actually being passed; a complete fix might also want to log/flag the divergent-nullability case, which is not done here.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟑 70 medium β€” react πŸ‘/πŸ‘Ž to teach the reviewer

if details.AdamID != nil && details.VPPAppTeamID != nil {
var labels []ActivitySoftwareLabel
labelQuery := `
SELECT
labels.id AS id,
Expand All @@ -213,8 +216,10 @@ func (ds *Datastore) ActivityDetailsForSoftwareTitleIcon(ctx context.Context, te
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &labels, labelQuery, details.VPPAppTeamID); err != nil {
return fleet.DetailsForSoftwareIconActivity{}, ctxerr.Wrap(ctx, err, "getting labels for software title icon")
}
allLabels = append(allLabels, labels...)
}
if details.InHouseAppID != nil {
var labels []ActivitySoftwareLabel
labelQuery := `
SELECT
labels.id AS id,
Expand All @@ -228,9 +233,10 @@ func (ds *Datastore) ActivityDetailsForSoftwareTitleIcon(ctx context.Context, te
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &labels, labelQuery, details.InHouseAppID); err != nil {
return fleet.DetailsForSoftwareIconActivity{}, ctxerr.Wrap(ctx, err, "getting labels for software title icon")
}
allLabels = append(allLabels, labels...)
}

for _, l := range labels {
for _, l := range allLabels {
switch {
case l.Exclude && !l.RequireAll:
details.LabelsExcludeAny = append(details.LabelsExcludeAny, fleet.ActivitySoftwareLabel{
Expand Down
3 changes: 2 additions & 1 deletion server/datastore/mysqlredis/host_cache_writes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ func TestWritePathInvalidation(t *testing.T) {
ds.UpdateHostRefetchCriticalQueriesUntilFunc = func(_ context.Context, _ uint, _ *time.Time) error { return nil }
},
invoke: func(ctx context.Context, d *Datastore, id uint, _ string) error {
return d.UpdateHostRefetchCriticalQueriesUntil(ctx, id, new(time.Unix(1, 0)))
until := time.Unix(1, 0)
return d.UpdateHostRefetchCriticalQueriesUntil(ctx, id, &until)
},
invoked: func(ds *mock.Store) bool { return ds.UpdateHostRefetchCriticalQueriesUntilFuncInvoked },
},
Comment on lines 95 to 102

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 πŸ”΄ time.Unix(1,0) passed to new() β€” invalid Go, test will not compile

In the UpdateHostRefetchCriticalQueriesUntil test case's invoke function (inside the singleHostWrappers slice literal in TestWritePathInvalidation), replaced the invalid new(time.Unix(1, 0)) call with a local variable until := time.Unix(1, 0) followed by d.UpdateHostRefetchCriticalQueriesUntil(ctx, id, &until), matching the suggested fix exactly and making the file valid, compilable Go.

πŸ€– Prompt for AI agents
In server/datastore/mysqlredis/host_cache_writes_test.go around line 91, review and complete this code-review fix: time.Unix(1,0) passed to new() β€” invalid Go, test will not compile.
What the draft fix changed: In the `UpdateHostRefetchCriticalQueriesUntil` test case's `invoke` function (inside the `singleHostWrappers` slice literal in `TestWritePathInvalidation`), replaced the invalid `new(time.Unix(1, 0))` call with a local variable `until := time.Unix(1, 0)` followed by `d.UpdateHostRefetchCriticalQueriesUntil(ctx, id, &until)`, matching the suggested fix exactly and making the file valid, compilable Go.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 96 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

Expand Down
Loading
Loading