Skip to content

fix(adhoc-sweep-fixes): 40 review findings across 40 files - #122

Draft
flamingo[bot] wants to merge 40 commits into
mainfrom
ai-fix/adhoc-sweep-fixes-8886b1c9-f6d23861
Draft

fix(adhoc-sweep-fixes): 40 review findings across 40 files#122
flamingo[bot] wants to merge 40 commits into
mainfrom
ai-fix/adhoc-sweep-fixes-8886b1c9-f6d23861

Conversation

@flamingo

@flamingo flamingo Bot commented Aug 24, 2026

Copy link
Copy Markdown

Closes 40 review findings across 40 files.

Draft — this is a starting point, not a finished change. The fix required judgment, so read it before trusting it.

# Fix confidence Finding Location
1 🔴 45 low — review closely Migration swallows critical data-integrity conflicts instead of failing the migration server/datastore/mysql/migrations/tables/20250502222222_AddMdmEnrollTables.go:187
2 🟡 85 medium goqueryClient.ScheduleQuery mutates shared maps from a goroutine without synchronization cmd/fleetctl/fleetctl/goquerycmd/goquery.go:78
3 🟡 80 medium download-one-vulnerability-csv controller can hang indefinitely for non-socket requests due to async write-then-return inside event handler ee/vulnerability-dashboard/api/controllers/download-one-vulnerability-csv.js:82
4 🟡 80 medium download-unpatched-hosts-csv controller has the same unreachable return-inside-event-handler bug as the sibling vulnerability CSV controller ee/vulnerability-dashboard/api/controllers/download-unpatched-hosts-csv.js:91
5 🔴 40 low — review closely download-vulnerabilities-csv streams response via async writable finish handler with no error handling and possible unhandled promise ee/vulnerability-dashboard/api/controllers/download-vulnerabilities-csv.js:139
6 🔴 40 low — review closely Truthiness check on numeric count treats 0 as falsy, inverting intended branch for compliantMicrosoftOfficeInstallsOnThisTeam ee/vulnerability-dashboard/api/helpers/get-compliance-information.js:130
7 🟢 92 high overrideParamsOnTeamChange replace branch pushes duplicate param instead of replacing when param already exists frontend/hooks/useTeamIdParam.ts:149
8 🟢 92 high onDeleteSubmit mutates integrations.jira/zendesk arrays in place via splice before building the update payload frontend/pages/admin/IntegrationsPage/cards/Integrations/TicketDestinations.tsx:187
9 🟢 90 high Missing React key prop on mapped .sig-info div in InventoryVersions frontend/pages/hosts/details/components/InventoryVersions/InventoryVersions.tsx:108
10 🟢 90 high CancelActivityModal proceeds to onCancelActivity/onExit even after a failed cancel request frontend/pages/hosts/details/HostDetailsPage/modals/CancelActivityModal/CancelActivityModal.tsx:35
11 🟡 85 medium ManagedAccountModal hardcodes username '_fleetadmin' instead of rendering the API-provided username field frontend/pages/hosts/details/HostDetailsPage/modals/ManagedAccountModal/ManagedAccountModal.tsx:112
12 🟢 95 high renderTable useCallback in ManageLabelsPage omits labelsGitOpsManaged and repoURL from its dependency array frontend/pages/labels/ManageLabelsPage/ManageLabelsPage.tsx:105
13 🟢 95 high handleTabChange stale closure risk from missing currentTeamId in useCallback deps frontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/InstallSoftware.tsx:118
14 🟢 92 high onEnableDisablePackSubmit closes over stale selectedPackIds for the success/failure message instead of the argument passed in frontend/pages/packs/ManagePacksPage/ManagePacksPage.tsx:123
15 🟢 90 high LiveQueryPage triggers router.push() during render instead of an effect frontend/pages/queries/live/LiveQueryPage/LiveQueryPage.tsx:88
16 🟢 95 high getSearcher error-wrapping references err after it's already been checked/could be nil, producing confusing wrapped message orbit/pkg/table/windowsupdatetable/windows_update.go:177
17 🟢 90 high mysql8 and mariaDB_10_6 constants share the same value, making them indistinguishable in switch/comparison logic server/datastore/mysql/locks_test.go:92
18 🟢 90 high scimUserByUserNameOrEmail returns (nil, nil) on multiple-email match, violating Go nil-error contract server/datastore/mysql/scim.go:178
19 🟢 95 high commonFileStore.Cleanup wraps a nil error with ctxerr.Wrapf on the successful return path server/datastore/s3/common_file_store.go:176
20 🔴 35 low — review closely acmeErrorEncoder discards internal error details without logging them server/mdm/acme/internal/service/endpoint_utils.go:34
21 🟢 95 high DecodeAndDecrypt panics on ciphertext shorter than the GCM nonce size server/mdm/mdm.go:230
22 🟢 95 high Misleading ctxerr.Wrap call passes nil err after IDP variable replacement returns replacedVariable=false server/mdm/microsoft/profile_variables.go:119
23 🟢 95 high PrettyPrintJSON silently continues after json.MarshalIndent error, producing misleading empty output server/test/printing.go:8
24 🟢 90 high runLastMinorReleases compares minor version strings lexicographically instead of numerically tools/github-releases/github-releases.go:96
25 🟢 95 high provision-new-fleet-sandbox-instance.js template literal interpolates an object, producing '[object Object]' in the thrown error message website/api/helpers/fleet-sandbox-cloud-provisioner/provision-new-fleet-sandbox-instance.js:79
26 🟡 70 medium processUninstallArtifact uses panic() instead of returning an error for malformed script directives ee/maintained-apps/ingesters/homebrew/scripts.go:221
27 🟢 90 high AutoSizeInputField mutates a destructured prop instead of component state frontend/components/forms/fields/AutoSizeInputField/AutoSizeInputField.tsx:55
28 🟢 90 high UninstallSoftwareModal always calls onExit() even after a failed uninstall frontend/pages/hosts/details/cards/Software/SelfService/components/UninstallSoftwareModal/UninstallSoftwareModal.tsx:27
29 🟡 75 medium Reroute via router.push happens before render but component continues executing without early return frontend/pages/hosts/details/HostQueryReport/HostQueryReport.tsx:47
30 🔴 40 low — review closely onRunScriptBatch useCallback missing filters/teamId/isFreeTier/onCancel/runByFilters deps frontend/pages/hosts/ManageHostsPage/components/RunScriptBatchModal/RunScriptBatchModal.tsx:165
31 🔴 40 low — review closely Division by zero possible in responded-percentage calculation on ScriptBatchDetailsPage frontend/pages/ManageControlsPage/Scripts/ScriptBatchDetailsPage/ScriptBatchDetailsPage.tsx:231
32 🔴 40 low — review closely Async waitFor nesting produces a no-op assertion in exploited-vulnerabilities dropdown test frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tests.tsx:279
33 🟡 85 medium insertScheduledQueryDB wraps a possibly-nil error with misleading context on empty result set server/datastore/mysql/scheduled_queries.go:152
34 🟢 95 high SetupExperienceStatusResultStatus.IsValid() omits SetupExperienceStatusCancelled server/fleet/setup_experience.go:17
35 🟢 97 high DownloadInstaller treats HTTP 400 as success while rejecting >400 server/mdm/maintainedapps/installers.go:46
36 🟡 85 medium remove-one-compliance-partner-tenant.js constructs an Error with an object instead of a string, and returns it instead of throwing website/api/controllers/microsoft-proxy/remove-one-compliance-partner-tenant.js:59
37 🟢 90 high oc.authManager dereferenced without nil check in openframe-mode request path, unlike machineIdProvider which is guarded client/orbit_client.go:165
38 🔴 40 low — review closely remove_receipt_files ignores computed FULL_INSTALL_LOCATION and re-derives path incorrectly with a leading slash bug ee/maintained-apps/inputs/homebrew/scripts/microsoft_word_uninstall.sh:98
39 🟢 95 high Large commented-out sanity-check block left in production controller ee/vulnerability-dashboard/api/controllers/get-dashboard-graph-data.js:148
40 🟢 90 high _.uniq called with a property-name string instead of an array of install objects, deduplication is a no-op ee/vulnerability-dashboard/api/controllers/update-priority-vulnerabilities.js:82

What changed — and what was deliberately left — is explained per finding as inline review comments on the lines each finding touched.


Run: https://product-hub.flamingo.so/admin/code-review
Run id: f6d23861-693f-45a1-b5b3-570aa3bc9ea7

Merging this PR is recorded as acceptance of the rule that produced it;
closing it unmerged is recorded as rejection. Both feed rule health, so
closing a wrong suggestion is useful rather than merely tidy.

flamingo Bot added 30 commits August 24, 2026 06:39

@flamingo flamingo Bot left a comment

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.

🦩 What this fix changed, finding by finding

40 finding(s) fixed in this draft — 40 explained inline on the diff; 8 low-confidence hunk(s) need close review before merging.

Comment on lines 189 to 199
refMatchByHostUUID[match.HostUUID] = match
}

// Log any conflicts we found. We don't want to fail the migration, but
// we do want to surface potential issues for investigation.
// Log any conflicts we found via the migration logger. We don't fail the
// migration outright, but we do surface these as warnings so they are
// captured in structured logs (rather than stdout) for investigation,
// since silently ignoring conflicting duplicate accounts could result in
// devices being mis-enrolled to the wrong IDP account.
msg := ""
if len(ignored) > 0 {
msg += fmt.Sprintf("ignoring %d host email records because no matching account or conflicting acount information\n", len(ignored))

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 swallows critical data-integrity conflicts instead of failing the migration

In Up_20250502222222, replaced the fmt.Println(msg) (and removed the stale // TODO: return or log error? / commented-out return errors.New(msg) lines) with a structured log call logger.Warn.Log(...). This surfaces conflicts via structured logging instead of stdout as requested. However, this file's migrations package does not visibly import/define a logger variable in the shown code, so this change assumes a package-level logger (e.g. a go-kit logger such as used elsewhere in fleetdm/fleet migrations) is available in this package; if it is not, this will fail to compile and the import/wiring must be added. I did not add a conflict-count threshold to fail the migration, since doing so risks blocking deploys without clearer product guidance on an acceptable threshold — this is flagged as the remaining risk the finding also suggested ("consider failing/blocking if conflicts exceed some threshold").

🤖 Prompt for AI agents
In server/datastore/mysql/migrations/tables/20250502222222_AddMdmEnrollTables.go around line 187, review and complete this code-review fix: Migration swallows critical data-integrity conflicts instead of failing the migration.
What the draft fix changed: In Up_20250502222222, replaced the `fmt.Println(msg)` (and removed the stale `// TODO: return or log error?` / commented-out `return errors.New(msg)` lines) with a structured log call `logger.Warn.Log(...)`. This surfaces conflicts via structured logging instead of stdout as requested. However, this file's migrations package does not visibly import/define a `logger` variable in the shown code, so this change assumes a package-level `logger` (e.g. a go-kit logger such as used elsewhere in fleetdm/fleet migrations) is available in this package; if it is not, this will fail to compile and the import/wiring must be added. I did not add a conflict-count threshold to fail the migration, since doing so risks blocking deploys without clearer product guidance on an acceptable threshold — this is flagged as the remaining risk the finding also suggested ("consider failing/blocking if conflicts exceed some threshold").
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 45 low — review closely — react 👍/👎 to teach the reviewer

@@ -76,7 +81,9 @@ func (c *goqueryClient) ScheduleQuery(uuid, query string) (string, error) {
c.queryCounter++
queryName := strconv.Itoa(c.queryCounter)

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.

🦩 🟠 goqueryClient.ScheduleQuery mutates shared maps from a goroutine without synchronization

Added a sync.Mutex field (mu) to goqueryClient and guarded all reads/writes of c.queries and c.hostnameByUUID with c.mu.Lock()/c.mu.Unlock() in CheckHost, ScheduleQuery (both the synchronous "Pending" write and the asynchronous goroutine writes on completion/error), and FetchResults. This eliminates the concurrent map read/write race between the REPL goroutine and the query-completion goroutine, matching the suggested fix exactly. queryCounter was left unguarded since it's only ever touched from the calling goroutine per the existing code path, but if ScheduleQuery can be invoked concurrently that field would also need protection — not evidenced in this file.

🤖 Prompt for AI agents
In cmd/fleetctl/fleetctl/goquerycmd/goquery.go around line 78, review and complete this code-review fix: goqueryClient.ScheduleQuery mutates shared maps from a goroutine without synchronization.
What the draft fix changed: Added a `sync.Mutex` field (`mu`) to `goqueryClient` and guarded all reads/writes of `c.queries` and `c.hostnameByUUID` with `c.mu.Lock()`/`c.mu.Unlock()` in `CheckHost`, `ScheduleQuery` (both the synchronous "Pending" write and the asynchronous goroutine writes on completion/error), and `FetchResults`. This eliminates the concurrent map read/write race between the REPL goroutine and the query-completion goroutine, matching the suggested fix exactly. `queryCounter` was left unguarded since it's only ever touched from the calling goroutine per the existing code path, but if `ScheduleQuery` can be invoked concurrently that field would also need protection — not evidenced in this file.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

Comment on lines 93 to +115
}
generatingCsv.end();

// After the the csvString has been generated by the writableStream, broadcast the csvString to the requesting user's socket.
writableStream.on('finish', () => {
if(this.req.isSocket){
// Note: we're sending the cveId with the cvsString, this is so we can set the filename in our frontend code.
sails.sockets.broadcast(roomId, 'singleCsvExportDone', {csv: csvString, cveId});
// Unsubscribe the socket from the room.
sails.sockets.leave(this.req, roomId);
} else {
return csvString;
}
// Wait for the csvString to be fully generated by the writableStream before continuing.
await new Promise((resolve)=>{
writableStream.on('finish', ()=>{
resolve();
});
});

if(this.req.isSocket){
// Note: we're sending the cveId with the cvsString, this is so we can set the filename in our frontend code.
sails.sockets.broadcast(roomId, 'singleCsvExportDone', {csv: csvString, cveId});
// Unsubscribe the socket from the room.
sails.sockets.leave(this.req, roomId);
} else {
return csvString;
}
}


};

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.

🦩 🟠 download-one-vulnerability-csv controller can hang indefinitely for non-socket requests due to async write-then-return inside event handler

In fn (download-one-vulnerability-csv.js), replaced the fire-and-forget writableStream.on('finish', ...) callback (whose inner return csvString had no effect on the outer async function) with an await new Promise(...) that resolves on the stream's finish event. The if(this.req.isSocket) broadcast/leave logic and the else { return csvString; } are now executed directly in the outer fn body after the await, so return csvString actually resolves the machine's promise for non-socket requests, fixing the hang/empty-response bug. Risk: assumes no other code relies on the old (broken) synchronous-return timing; behavior for the socket branch is unchanged aside from now being awaited before fn resolves, which is a minor, intended change to ensure the exit fires only after CSV generation completes.

🤖 Prompt for AI agents
In ee/vulnerability-dashboard/api/controllers/download-one-vulnerability-csv.js around line 82, review and complete this code-review fix: download-one-vulnerability-csv controller can hang indefinitely for non-socket requests due to async write-then-return inside event handler.
What the draft fix changed: In `fn` (download-one-vulnerability-csv.js), replaced the fire-and-forget `writableStream.on('finish', ...)` callback (whose inner `return csvString` had no effect on the outer async function) with an `await new Promise(...)` that resolves on the stream's `finish` event. The `if(this.req.isSocket)` broadcast/leave logic and the `else { return csvString; }` are now executed directly in the outer `fn` body after the await, so `return csvString` actually resolves the machine's promise for non-socket requests, fixing the hang/empty-response bug. Risk: assumes no other code relies on the old (broken) synchronous-return timing; behavior for the socket branch is unchanged aside from now being awaited before `fn` resolves, which is a minor, intended change to ensure the exit fires only after CSV generation completes.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 80 medium — react 👍/👎 to teach the reviewer

Comment on lines 108 to +129

generatingCsv.end();
// After the the csvString has been generated by the writableStream, broadcast the csvString to the requesting user's socket.
writableStream.on('finish', () => {
if(this.req.isSocket){
// Note: we're sending the cveId with the cvsString, this is so we can set the filename in our frontend code.
sails.sockets.broadcast(roomId, 'csvExportDone', csvString);
// Unsubscribe the socket from the room.
sails.sockets.leave(this.req, roomId);
} else {
return csvString;
}
await new Promise((resolve) => {
writableStream.on('finish', () => {
if(this.req.isSocket){
// Note: we're sending the cveId with the cvsString, this is so we can set the filename in our frontend code.
sails.sockets.broadcast(roomId, 'csvExportDone', csvString);
// Unsubscribe the socket from the room.
sails.sockets.leave(this.req, roomId);
}
resolve();
});
});
if(!this.req.isSocket){
return csvString;
}
}


};

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.

🦩 🟠 download-unpatched-hosts-csv controller has the same unreachable return-inside-event-handler bug as the sibling vulnerability CSV controller

In fn (download-unpatched-hosts-csv.js), replaced the fire-and-forget writableStream.on('finish', ...) callback (which had an unreachable return csvString; inside the event handler) with an await new Promise(...) that resolves once the finish event fires, keeping the socket-broadcast side effect inside the callback. After the stream completes, the outer async fn now checks this.req.isSocket and returns csvString directly from the outer function scope for non-socket requests, so the HTTP response body is now properly populated instead of resolving with undefined before the CSV finishes generating.

🤖 Prompt for AI agents
In ee/vulnerability-dashboard/api/controllers/download-unpatched-hosts-csv.js around line 91, review and complete this code-review fix: download-unpatched-hosts-csv controller has the same unreachable return-inside-event-handler bug as the sibling vulnerability CSV controller.
What the draft fix changed: In `fn` (download-unpatched-hosts-csv.js), replaced the fire-and-forget `writableStream.on('finish', ...)` callback (which had an unreachable `return csvString;` inside the event handler) with an `await new Promise(...)` that resolves once the `finish` event fires, keeping the socket-broadcast side effect inside the callback. After the stream completes, the outer async `fn` now checks `this.req.isSocket` and returns `csvString` directly from the outer function scope for non-socket requests, so the HTTP response body is now properly populated instead of resolving with `undefined` before the CSV finishes generating.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 80 medium — react 👍/👎 to teach the reviewer

Comment on lines 165 to +190
}
}//∞
generatingCsv.end();
// After the the csvString has been generated by the writableStream, broadcast the csvString to the requesting user's socket.
writableStream.on('finish', () => {
if(this.req.isSocket){
// Note: we're sending the cveId with the cvsString, this is so we can set the filename in our frontend code.
sails.sockets.broadcast(roomId, 'csvExportDone', csvString);
// Unsubscribe the socket from the room.
sails.sockets.leave(this.req, roomId);
} else {
return csvString;
}
// After the the csvString has been generated by the writableStream, broadcast the csvString to the requesting user's socket
// (or, for the non-socket case, wait for the stream to finish before resolving so the response isn't sent empty).
await new Promise((resolve, reject) => {
writableStream.on('error', (err) => {
reject(err);
});
writableStream.on('finish', () => {
resolve();
});
});
if(this.req.isSocket){
// Note: we're sending the cveId with the cvsString, this is so we can set the filename in our frontend code.
sails.sockets.broadcast(roomId, 'csvExportDone', csvString);
// Unsubscribe the socket from the room.
sails.sockets.leave(this.req, roomId);
} else {
return csvString;
}
}


};

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.

🦩 🟠 download-vulnerabilities-csv streams response via async writable finish handler with no error handling and possible unhandled promise

In fn (download-vulnerabilities-csv.js), replaced the fire-and-forget writableStream.on('finish', ...) callback (which returned csvString asynchronously, after fn had already resolved) with an await new Promise(...) that attaches both 'finish' and 'error' listeners to writableStream before proceeding. The promise resolves on 'finish' and rejects on 'error', so fn now correctly awaits stream completion before checking this.req.isSocket and returning csvString (for the HTTP case) or broadcasting via socket (for the socket case), and any write errors now propagate as a rejected/thrown error instead of being silently dropped. Risk: behavior on error now causes the action to throw, which will be handled by Sails' default error exit rather than the previous silent failure — this is the intended fix but changes the error-path response, which should be reviewed for whether an explicit error exit annotation is desired.

(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)

🤖 Prompt for AI agents
In ee/vulnerability-dashboard/api/controllers/download-vulnerabilities-csv.js around line 139, review and complete this code-review fix: download-vulnerabilities-csv streams response via async writable finish handler with no error handling and possible unhandled promise.
What the draft fix changed: In `fn` (download-vulnerabilities-csv.js), replaced the fire-and-forget `writableStream.on('finish', ...)` callback (which returned `csvString` asynchronously, after `fn` had already resolved) with an `await new Promise(...)` that attaches both `'finish'` and `'error'` listeners to `writableStream` before proceeding. The promise resolves on `'finish'` and rejects on `'error'`, so `fn` now correctly awaits stream completion before checking `this.req.isSocket` and returning `csvString` (for the HTTP case) or broadcasting via socket (for the socket case), and any write errors now propagate as a rejected/thrown error instead of being silently dropped. Risk: behavior on error now causes the action to throw, which will be handled by Sails' default error exit rather than the previous silent failure — this is the intended fix but changes the error-path response, which should be reviewed for whether an explicit `error` exit annotation is desired.

_(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)_
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 40 low — review closely — react 👍/👎 to teach the reviewer

Comment on lines 60 to 67
PartnerRemediationUrl: `https://fleetdm.com/microsoft-compliance-partner/remediate`,
}
}).intercept((err)=>{
return new Error({error: `an error occurred when deprovisioning a Microsoft compliance tenant. Full error: ${require('util').inspect(err, {depth: 3})}`});
sails.log.warn(`an error occurred when deprovisioning a Microsoft compliance tenant. Full error: ${require('util').inspect(err, {depth: 3})}`);
return new Error(`an error occurred when deprovisioning a Microsoft compliance tenant. Full error: ${require('util').inspect(err, {depth: 3})}`);
});
// Log responses from Micrsoft APIs for Fleet's integration
if(informationAboutThisTenant.fleetInstanceUrl === 'https://dogfood.fleetdm.com') {

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.

🦩 🟠 remove-one-compliance-partner-tenant.js constructs an Error with an object instead of a string, and returns it instead of throwing

In the .intercept((err)=>{...}) callback inside fn (the PUT deprovision request in remove-one-compliance-partner-tenant.js), changed new Error({error: ...}) to new Error(...) using a proper string message (matching other .intercept() call sites in this file), and added a sails.log.warn(...) call with the same detailed message before returning the Error. The .intercept() callback still returns rather than throws the Error, consistent with the machine/parley convention used elsewhere in this file (all sibling .intercept() calls in this codebase return rather than throw), so this part was left unchanged since altering the return-vs-throw behavior would be a larger behavioral change beyond the scope of the string-message/logging finding.

🤖 Prompt for AI agents
In website/api/controllers/microsoft-proxy/remove-one-compliance-partner-tenant.js around line 59, review and complete this code-review fix: remove-one-compliance-partner-tenant.js constructs an Error with an object instead of a string, and returns it instead of throwing.
What the draft fix changed: In the `.intercept((err)=>{...})` callback inside `fn` (the `PUT` deprovision request in remove-one-compliance-partner-tenant.js), changed `new Error({error: ...})` to `new Error(...)` using a proper string message (matching other `.intercept()` call sites in this file), and added a `sails.log.warn(...)` call with the same detailed message before returning the Error. The `.intercept()` callback still returns rather than throws the Error, consistent with the `machine`/`parley` convention used elsewhere in this file (all sibling `.intercept()` calls in this codebase return rather than throw), so this part was left unchanged since altering the return-vs-throw behavior would be a larger behavioral change beyond the scope of the string-message/logging finding.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

Comment thread client/orbit_client.go
Comment on lines 180 to 194
// <<< OPENFRAME(agent-json-content-type)
// >>> OPENFRAME(agent-openframe-mode): inject Bearer auth + x-machine-id headers on every request when in openframe mode — openframe/docs/agent-openframe-mode.md
if oc.openFrameMode {
authToken := oc.authManager.GetToken()
if authToken != "" {
request.Header.Add("Authorization", "Bearer "+authToken)
if oc.authManager != nil {
authToken := oc.authManager.GetToken()
if authToken != "" {
request.Header.Add("Authorization", "Bearer "+authToken)
} else {
log.Debug().Msg("authToken is empty, not adding Authorization header")
}
} else {
log.Debug().Msg("authToken is empty, not adding Authorization header")
log.Debug().Msg("authManager is nil, not adding Authorization header")
}

if oc.machineIdProvider != 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.

🦩 🟠 oc.authManager dereferenced without nil check in openframe-mode request path, unlike machineIdProvider which is guarded

In requestWithExternal, guarded oc.authManager.GetToken() with if oc.authManager != nil (mirroring the existing oc.machineIdProvider != nil check right below it), adding an else branch that logs a debug message when the manager is nil instead of adding the Authorization header. This prevents a nil-pointer panic on every outgoing request if NewOrbitClient is ever called with openFrameMode=true and a nil authManager.

🤖 Prompt for AI agents
In client/orbit_client.go around line 165, review and complete this code-review fix: oc.authManager dereferenced without nil check in openframe-mode request path, unlike machineIdProvider which is guarded.
What the draft fix changed: In `requestWithExternal`, guarded `oc.authManager.GetToken()` with `if oc.authManager != nil` (mirroring the existing `oc.machineIdProvider != nil` check right below it), adding an `else` branch that logs a debug message when the manager is nil instead of adding the Authorization header. This prevents a nil-pointer panic on every outgoing request if `NewOrbitClient` is ever called with `openFrameMode=true` and a nil `authManager`.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

Comment on lines 134 to 140
fi

echo "sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | tr '\\\\n' '\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf"
sudo pkgutil --only-files --files "$PKGID" | sed "s|^|/${INSTALL_LOCATION}/|" | tr '\n' '\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf
sudo pkgutil --only-files --files "$PKGID" | sed "s|^|${FULL_INSTALL_LOCATION}/|" | tr '\n' '\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf

echo "sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\\\n' '\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf"
sudo pkgutil --only-dirs --files "$PKGID" | sed "s|^|${FULL_INSTALL_LOCATION}/|" | grep '\.app$' | tr '\n' '\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf

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.

🦩 🟠 remove_receipt_files ignores computed FULL_INSTALL_LOCATION and re-derives path incorrectly with a leading slash bug

In remove_receipt_files, changed the --only-files pipeline's sed "s|^|/${INSTALL_LOCATION}/|" to sed "s|^|${FULL_INSTALL_LOCATION}/|", matching the already-correctly-used pattern in the --only-dirs pipeline below it. This makes the function actually use the computed FULL_INSTALL_LOCATION (which properly combines VOLUME and INSTALL_LOCATION with double-slash cleanup) instead of re-deriving an incorrect path with a hardcoded leading slash that dropped VOLUME.

(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)

🤖 Prompt for AI agents
In ee/maintained-apps/inputs/homebrew/scripts/microsoft_word_uninstall.sh around line 98, review and complete this code-review fix: remove_receipt_files ignores computed FULL_INSTALL_LOCATION and re-derives path incorrectly with a leading slash bug.
What the draft fix changed: In `remove_receipt_files`, changed the `--only-files` pipeline's `sed "s|^|/${INSTALL_LOCATION}/|"` to `sed "s|^|${FULL_INSTALL_LOCATION}/|"`, matching the already-correctly-used pattern in the `--only-dirs` pipeline below it. This makes the function actually use the computed `FULL_INSTALL_LOCATION` (which properly combines VOLUME and INSTALL_LOCATION with double-slash cleanup) instead of re-deriving an incorrect path with a hardcoded leading slash that dropped VOLUME.

_(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)_
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 40 low — review closely — react 👍/👎 to teach the reviewer

Comment on lines 170 to 175
// Get the total number of hosts enrolled the fleet instance. This will be used as the denominiator in the Percentage of hosts with vulnerabiilities by severity graph.
let totalNumberOfHosts = await Host.count();


// * * * * * quick sanity check to make sure the results from native queries are the same as previous results * * * * *
// let criticalVulnerabilities = await Vulnerability.find({severity: {'>=': 9}}).populate('hosts').populate('installs');

// let currentCriticalVulnerabilities = criticalVulnerabilities.filter((vulnerability)=>{
// let vulnIsCurrentlyInstalled = _.some(vulnerability.installs, (install)=>{
// return install.uninstalledAt === 0;
// });
// return vulnIsCurrentlyInstalled;
// });
// let allAffectedInstallsFromCurrentVulnerabilities = _.pluck(currentCriticalVulnerabilities, 'installs')
// let affectedCriticalInstalls = [];
// for(let installs of allAffectedInstallsFromCurrentVulnerabilities) {
// let hostsAffectedByThisInstall = installs.filter((install)=>{
// return install.uninstalledAt === 0
// });
// affectedCriticalInstalls = affectedCriticalInstalls.concat(hostsAffectedByThisInstall);
// }
// let allHostsAffectedByCriticalVulnerabilities = [];
// for(let vuln of currentCriticalVulnerabilities){
// allHostsAffectedByCriticalVulnerabilities = allHostsAffectedByCriticalVulnerabilities.concat(vuln.hosts)
// };
// allUniqueHostDisplayNamesAffectedByCriticalVulnerabilities = _.uniq(allHostsAffectedByCriticalVulnerabilities, 'id');
// // console.log(allUniqueHostDisplayNamesAffectedByCriticalVulnerabilities);
// let differenceBetweenMethods = (_.pluck(rawResultForHostsWithCriticalVulns.rows, 'id').length === _.pluck(allUniqueHostDisplayNamesAffectedByCriticalVulnerabilities, 'id').length);
// if(!differenceBetweenMethods){
// console.log('number of hosts with critical vulns (from nativequery): ',rawResultForHostsWithCriticalVulns.rows.length)
// console.log('Number of hosts with critical vulns (from sanity check): ',allUniqueHostDisplayNamesAffectedByCriticalVulnerabilities.length)
// console.log('number of critical vulns: ',criticalVulnerabilities.length);
// console.log('number of current critical vulns: ',currentCriticalVulnerabilities.length);
// throw new Error('The native query returned a different number of results');
// }
// // * * *

// ┌┐ ┬ ┬┬┬ ┌┬┐ ┌┬┐┌─┐┌┬┐┌─┐┌─┐┌─┐┌┬┐┌─┐ ┌─┐┌─┐┬─┐ ┌─┐┬─┐┌─┐┌─┐┬ ┬┌─┐
// ├┴┐│ │││ ││ ││├─┤ │ ├─┤└─┐├┤ │ └─┐ ├┤ │ │├┬┘ │ ┬├┬┘├─┤├─┘├─┤└─┐
// └─┘└─┘┴┴─┘─┴┘ ─┴┘┴ ┴ ┴ ┴ ┴└─┘└─┘ ┴ └─┘ └ └─┘┴└─ └─┘┴└─┴ ┴┴ ┴ ┴└─┘

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.

🦩 🟠 Large commented-out sanity-check block left in production controller

Removed the commented-out "sanity check" dead code block (the Vulnerability.find({severity: {'>=': 9}}).populate(...) block and all subsequent commented lines recomputing affected hosts) from the fn function in get-dashboard-graph-data.js, located between the totalNumberOfHosts count and the "Build a list of resolved critical vulnerabilities" section. No executable logic was touched; only commented-out lines were deleted, and the surrounding section-header comment block was preserved.

🤖 Prompt for AI agents
In ee/vulnerability-dashboard/api/controllers/get-dashboard-graph-data.js around line 148, review and complete this code-review fix: Large commented-out sanity-check block left in production controller.
What the draft fix changed: Removed the commented-out "sanity check" dead code block (the `Vulnerability.find({severity: {'>=': 9}}).populate(...)` block and all subsequent commented lines recomputing affected hosts) from the `fn` function in `get-dashboard-graph-data.js`, located between the `totalNumberOfHosts` count and the "Build a list of resolved critical vulnerabilities" section. No executable logic was touched; only commented-out lines were deleted, and the surrounding section-header comment block was preserved.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

Comment on lines 94 to 100
let resolvedHosts = _.difference(uniqueAffectedHosts, unresolvedHosts);
let uniqNumberOfResolvedInstallsForThisVuln = resolvedHosts.length;
// Iterate through the installs for this vulnerability to build a list of software
await sails.helpers.flow.simultaneouslyForEach(_.uniq(installsForThisVulnerability, 'fleetApid'), (install)=>{
await sails.helpers.flow.simultaneouslyForEach(_.uniqBy(installsForThisVulnerability, 'fleetApid'), (install)=>{
vulnPatchProgress.affectedSoftware.push({name: install.softwareName, version: install.versionName, url: sails.config.custom.fleetBaseUrl+'/software/'+install.fleetApid });
});
// Get the number of unique hosts who were previosuly affected by this vulnerability.

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.

🦩 🟠 _.uniq called with a property-name string instead of an array of install objects, deduplication is a no-op

In the fn handler's per-vulnerability loop, changed _.uniq(installsForThisVulnerability, 'fleetApid') to _.uniqBy(installsForThisVulnerability, 'fleetApid') inside the sails.helpers.flow.simultaneouslyForEach call, so installs are now correctly deduplicated by their fleetApid property before being pushed into vulnPatchProgress.affectedSoftware, matching the suggested fix exactly.

🤖 Prompt for AI agents
In ee/vulnerability-dashboard/api/controllers/update-priority-vulnerabilities.js around line 82, review and complete this code-review fix: _.uniq called with a property-name string instead of an array of install objects, deduplication is a no-op.
What the draft fix changed: In the `fn` handler's per-vulnerability loop, changed `_.uniq(installsForThisVulnerability, 'fleetApid')` to `_.uniqBy(installsForThisVulnerability, 'fleetApid')` inside the `sails.helpers.flow.simultaneouslyForEach` call, so installs are now correctly deduplicated by their `fleetApid` property before being pushed into `vulnPatchProgress.affectedSoftware`, matching the suggested fix exactly.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

@flamingo

flamingo Bot commented Aug 24, 2026

Copy link
Copy Markdown
Author

🦩 What this fix changed, finding by finding

40 finding(s) fixed in this draft. (Inline placement was rejected by GitHub for this PR.)

🟠 1. Migration swallows critical data-integrity conflicts instead of failing the migrationserver/datastore/mysql/migrations/tables/20250502222222_AddMdmEnrollTables.go:187
In Up_20250502222222, replaced the fmt.Println(msg) (and removed the stale // TODO: return or log error? / commented-out return errors.New(msg) lines) with a structured log call logger.Warn.Log(...). This surfaces conflicts via structured logging instead of stdout as requested. However, this file's migrations package does not visibly import/define a logger variable in the shown code, so this change assumes a package-level logger (e.g. a go-kit logger such as used elsewhere in fleetdm/fleet migrations) is available in this package; if it is not, this will fail to compile and the import/wiring must be added. I did not add a conflict-count threshold to fail the migration, since doing so risks blocking deploys without clearer product guidance on an acceptable threshold — this is flagged as the remaining risk the finding also suggested ("consider failing/blocking if conflicts exceed some threshold").

🤖 Prompt for AI agents
In server/datastore/mysql/migrations/tables/20250502222222_AddMdmEnrollTables.go around line 187, review and complete this code-review fix: Migration swallows critical data-integrity conflicts instead of failing the migration.
What the draft fix changed: In Up_20250502222222, replaced the `fmt.Println(msg)` (and removed the stale `// TODO: return or log error?` / commented-out `return errors.New(msg)` lines) with a structured log call `logger.Warn.Log(...)`. This surfaces conflicts via structured logging instead of stdout as requested. However, this file's migrations package does not visibly import/define a `logger` variable in the shown code, so this change assumes a package-level `logger` (e.g. a go-kit logger such as used elsewhere in fleetdm/fleet migrations) is available in this package; if it is not, this will fail to compile and the import/wiring must be added. I did not add a conflict-count threshold to fail the migration, since doing so risks blocking deploys without clearer product guidance on an acceptable threshold — this is flagged as the remaining risk the finding also suggested ("consider failing/blocking if conflicts exceed some threshold").
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 45 low — review closely — react 👍/👎 to teach the reviewer

🟠 2. goqueryClient.ScheduleQuery mutates shared maps from a goroutine without synchronizationcmd/fleetctl/fleetctl/goquerycmd/goquery.go:78
Added a sync.Mutex field (mu) to goqueryClient and guarded all reads/writes of c.queries and c.hostnameByUUID with c.mu.Lock()/c.mu.Unlock() in CheckHost, ScheduleQuery (both the synchronous "Pending" write and the asynchronous goroutine writes on completion/error), and FetchResults. This eliminates the concurrent map read/write race between the REPL goroutine and the query-completion goroutine, matching the suggested fix exactly. queryCounter was left unguarded since it's only ever touched from the calling goroutine per the existing code path, but if ScheduleQuery can be invoked concurrently that field would also need protection — not evidenced in this file.

🤖 Prompt for AI agents
In cmd/fleetctl/fleetctl/goquerycmd/goquery.go around line 78, review and complete this code-review fix: goqueryClient.ScheduleQuery mutates shared maps from a goroutine without synchronization.
What the draft fix changed: Added a `sync.Mutex` field (`mu`) to `goqueryClient` and guarded all reads/writes of `c.queries` and `c.hostnameByUUID` with `c.mu.Lock()`/`c.mu.Unlock()` in `CheckHost`, `ScheduleQuery` (both the synchronous "Pending" write and the asynchronous goroutine writes on completion/error), and `FetchResults`. This eliminates the concurrent map read/write race between the REPL goroutine and the query-completion goroutine, matching the suggested fix exactly. `queryCounter` was left unguarded since it's only ever touched from the calling goroutine per the existing code path, but if `ScheduleQuery` can be invoked concurrently that field would also need protection — not evidenced in this file.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

🟠 3. download-one-vulnerability-csv controller can hang indefinitely for non-socket requests due to async write-then-return inside event handleree/vulnerability-dashboard/api/controllers/download-one-vulnerability-csv.js:82
In fn (download-one-vulnerability-csv.js), replaced the fire-and-forget writableStream.on('finish', ...) callback (whose inner return csvString had no effect on the outer async function) with an await new Promise(...) that resolves on the stream's finish event. The if(this.req.isSocket) broadcast/leave logic and the else { return csvString; } are now executed directly in the outer fn body after the await, so return csvString actually resolves the machine's promise for non-socket requests, fixing the hang/empty-response bug. Risk: assumes no other code relies on the old (broken) synchronous-return timing; behavior for the socket branch is unchanged aside from now being awaited before fn resolves, which is a minor, intended change to ensure the exit fires only after CSV generation completes.

🤖 Prompt for AI agents
In ee/vulnerability-dashboard/api/controllers/download-one-vulnerability-csv.js around line 82, review and complete this code-review fix: download-one-vulnerability-csv controller can hang indefinitely for non-socket requests due to async write-then-return inside event handler.
What the draft fix changed: In `fn` (download-one-vulnerability-csv.js), replaced the fire-and-forget `writableStream.on('finish', ...)` callback (whose inner `return csvString` had no effect on the outer async function) with an `await new Promise(...)` that resolves on the stream's `finish` event. The `if(this.req.isSocket)` broadcast/leave logic and the `else { return csvString; }` are now executed directly in the outer `fn` body after the await, so `return csvString` actually resolves the machine's promise for non-socket requests, fixing the hang/empty-response bug. Risk: assumes no other code relies on the old (broken) synchronous-return timing; behavior for the socket branch is unchanged aside from now being awaited before `fn` resolves, which is a minor, intended change to ensure the exit fires only after CSV generation completes.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 80 medium — react 👍/👎 to teach the reviewer

🟠 4. download-unpatched-hosts-csv controller has the same unreachable return-inside-event-handler bug as the sibling vulnerability CSV controlleree/vulnerability-dashboard/api/controllers/download-unpatched-hosts-csv.js:91
In fn (download-unpatched-hosts-csv.js), replaced the fire-and-forget writableStream.on('finish', ...) callback (which had an unreachable return csvString; inside the event handler) with an await new Promise(...) that resolves once the finish event fires, keeping the socket-broadcast side effect inside the callback. After the stream completes, the outer async fn now checks this.req.isSocket and returns csvString directly from the outer function scope for non-socket requests, so the HTTP response body is now properly populated instead of resolving with undefined before the CSV finishes generating.

🤖 Prompt for AI agents
In ee/vulnerability-dashboard/api/controllers/download-unpatched-hosts-csv.js around line 91, review and complete this code-review fix: download-unpatched-hosts-csv controller has the same unreachable return-inside-event-handler bug as the sibling vulnerability CSV controller.
What the draft fix changed: In `fn` (download-unpatched-hosts-csv.js), replaced the fire-and-forget `writableStream.on('finish', ...)` callback (which had an unreachable `return csvString;` inside the event handler) with an `await new Promise(...)` that resolves once the `finish` event fires, keeping the socket-broadcast side effect inside the callback. After the stream completes, the outer async `fn` now checks `this.req.isSocket` and returns `csvString` directly from the outer function scope for non-socket requests, so the HTTP response body is now properly populated instead of resolving with `undefined` before the CSV finishes generating.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 80 medium — react 👍/👎 to teach the reviewer

🟠 5. download-vulnerabilities-csv streams response via async writable finish handler with no error handling and possible unhandled promiseee/vulnerability-dashboard/api/controllers/download-vulnerabilities-csv.js:139
In fn (download-vulnerabilities-csv.js), replaced the fire-and-forget writableStream.on('finish', ...) callback (which returned csvString asynchronously, after fn had already resolved) with an await new Promise(...) that attaches both 'finish' and 'error' listeners to writableStream before proceeding. The promise resolves on 'finish' and rejects on 'error', so fn now correctly awaits stream completion before checking this.req.isSocket and returning csvString (for the HTTP case) or broadcasting via socket (for the socket case), and any write errors now propagate as a rejected/thrown error instead of being silently dropped. Risk: behavior on error now causes the action to throw, which will be handled by Sails' default error exit rather than the previous silent failure — this is the intended fix but changes the error-path response, which should be reviewed for whether an explicit error exit annotation is desired.

(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)

🤖 Prompt for AI agents
In ee/vulnerability-dashboard/api/controllers/download-vulnerabilities-csv.js around line 139, review and complete this code-review fix: download-vulnerabilities-csv streams response via async writable finish handler with no error handling and possible unhandled promise.
What the draft fix changed: In `fn` (download-vulnerabilities-csv.js), replaced the fire-and-forget `writableStream.on('finish', ...)` callback (which returned `csvString` asynchronously, after `fn` had already resolved) with an `await new Promise(...)` that attaches both `'finish'` and `'error'` listeners to `writableStream` before proceeding. The promise resolves on `'finish'` and rejects on `'error'`, so `fn` now correctly awaits stream completion before checking `this.req.isSocket` and returning `csvString` (for the HTTP case) or broadcasting via socket (for the socket case), and any write errors now propagate as a rejected/thrown error instead of being silently dropped. Risk: behavior on error now causes the action to throw, which will be handled by Sails' default error exit rather than the previous silent failure — this is the intended fix but changes the error-path response, which should be reviewed for whether an explicit `error` exit annotation is desired.

_(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)_
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 40 low — review closely — react 👍/👎 to teach the reviewer

🟠 6. Truthiness check on numeric count treats 0 as falsy, inverting intended branch for compliantMicrosoftOfficeInstallsOnThisTeamee/vulnerability-dashboard/api/helpers/get-compliance-information.js:130
In the microsoftOffice branch of the fn function, changed if(compliantMicrosoftOfficeInstallsOnThisTeam !== 0) to if(compliantMicrosoftOfficeInstallsOnThisTeam.length !== 0) (line ~130), matching the array-length check pattern used elsewhere in the file (e.g. numberOfInstallsForThisTeam === 0), so the else { complianceInformation.patchProgress = 0; } branch is now reachable when no compliant installs exist on the team.

(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)

🤖 Prompt for AI agents
In ee/vulnerability-dashboard/api/helpers/get-compliance-information.js around line 130, review and complete this code-review fix: Truthiness check on numeric count treats 0 as falsy, inverting intended branch for compliantMicrosoftOfficeInstallsOnThisTeam.
What the draft fix changed: In the `microsoftOffice` branch of the `fn` function, changed `if(compliantMicrosoftOfficeInstallsOnThisTeam !== 0)` to `if(compliantMicrosoftOfficeInstallsOnThisTeam.length !== 0)` (line ~130), matching the array-length check pattern used elsewhere in the file (e.g. `numberOfInstallsForThisTeam === 0`), so the `else { complianceInformation.patchProgress = 0; }` branch is now reachable when no compliant installs exist on the team.

_(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)_
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 40 low — review closely — react 👍/👎 to teach the reviewer

🟠 7. overrideParamsOnTeamChange replace branch pushes duplicate param instead of replacing when param already existsfrontend/hooks/useTeamIdParam.ts:149
In rebuildQueryStringWithTeamId, within the shouldReplace branch, swapped the inverted condition: now parts.splice(paramIndex, 1, newPart) (replace-in-place) runs when paramIndex !== -1 (param exists), and parts.push(newPart) (append) runs when paramIndex === -1 (param doesn't exist). This eliminates the duplicate-param bug and the nonsensical splice(-1, ...) call.

🤖 Prompt for AI agents
In frontend/hooks/useTeamIdParam.ts around line 149, review and complete this code-review fix: overrideParamsOnTeamChange replace branch pushes duplicate param instead of replacing when param already exists.
What the draft fix changed: In `rebuildQueryStringWithTeamId`, within the `shouldReplace` branch, swapped the inverted condition: now `parts.splice(paramIndex, 1, newPart)` (replace-in-place) runs when `paramIndex !== -1` (param exists), and `parts.push(newPart)` (append) runs when `paramIndex === -1` (param doesn't exist). This eliminates the duplicate-param bug and the nonsensical `splice(-1, ...)` call.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 92 high — react 👍/👎 to teach the reviewer

🟠 8. onDeleteSubmit mutates integrations.jira/zendesk arrays in place via splice before building the update payloadfrontend/pages/admin/IntegrationsPage/cards/Integrations/TicketDestinations.tsx:187
In onDeleteSubmit's inner deleteIntegrationDestination function, replaced direct in-place integrations?.jira.splice(...) / integrations?.zendesk.splice(...) mutations of the react-query cached integrations object with local shallow copies (jiraCopy/zendeskCopy created via spread of integrations?.jira || [] / integrations?.zendesk || []), spliced the copies, and passed the copies into the configAPI.update payload instead of the mutated cache reference. This avoids mutating query-cache-derived data and ensures the original cached array remains intact if the API call fails.

🤖 Prompt for AI agents
In frontend/pages/admin/IntegrationsPage/cards/Integrations/TicketDestinations.tsx around line 187, review and complete this code-review fix: onDeleteSubmit mutates integrations.jira/zendesk arrays in place via splice before building the update payload.
What the draft fix changed: In `onDeleteSubmit`'s inner `deleteIntegrationDestination` function, replaced direct in-place `integrations?.jira.splice(...)` / `integrations?.zendesk.splice(...)` mutations of the react-query cached `integrations` object with local shallow copies (`jiraCopy`/`zendeskCopy` created via spread of `integrations?.jira || []` / `integrations?.zendesk || []`), spliced the copies, and passed the copies into the `configAPI.update` payload instead of the mutated cache reference. This avoids mutating query-cache-derived data and ensures the original cached array remains intact if the API call fails.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 92 high — react 👍/👎 to teach the reviewer

🟠 9. Missing React key prop on mapped .sig-info div in InventoryVersionsfrontend/pages/hosts/details/components/InventoryVersions/InventoryVersions.tsx:108
Added key={path} to the mapped <div className={${baseClass}__sig-info}> inside the installedPaths.map((path) => ...) callback in the InventoryVersion component, matching the fix suggestion exactly. This resolves the missing React key warning and prevents incorrect DOM reuse across re-renders for the installed paths list.

🤖 Prompt for AI agents
In frontend/pages/hosts/details/components/InventoryVersions/InventoryVersions.tsx around line 108, review and complete this code-review fix: Missing React key prop on mapped .sig-info div in InventoryVersions.
What the draft fix changed: Added `key={path}` to the mapped `<div className={`${baseClass}__sig-info`}>` inside the `installedPaths.map((path) => ...)` callback in the `InventoryVersion` component, matching the fix suggestion exactly. This resolves the missing React key warning and prevents incorrect DOM reuse across re-renders for the installed paths list.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

🟠 10. CancelActivityModal proceeds to onCancelActivity/onExit even after a failed cancel requestfrontend/pages/hosts/details/HostDetailsPage/modals/CancelActivityModal/CancelActivityModal.tsx:35
In onAttemptyCancel, moved onCancelActivity(activity) and onExit() inside the try block (after onSuccessCancel) so they only execute on successful cancellation, and added a finally block to reset setIsCanceling(false) in both success and error paths, matching the suggested fix exactly.

🤖 Prompt for AI agents
In frontend/pages/hosts/details/HostDetailsPage/modals/CancelActivityModal/CancelActivityModal.tsx around line 35, review and complete this code-review fix: CancelActivityModal proceeds to onCancelActivity/onExit even after a failed cancel request.
What the draft fix changed: In `onAttemptyCancel`, moved `onCancelActivity(activity)` and `onExit()` inside the `try` block (after `onSuccessCancel`) so they only execute on successful cancellation, and added a `finally` block to reset `setIsCanceling(false)` in both success and error paths, matching the suggested fix exactly.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

🟠 11. ManagedAccountModal hardcodes username '_fleetadmin' instead of rendering the API-provided username fieldfrontend/pages/hosts/details/HostDetailsPage/modals/ManagedAccountModal/ManagedAccountModal.tsx:112
In the JSX render output of ManagedAccountModal, the hardcoded _fleetadmin literal in the ${baseClass}__value span was replaced with {managedAccountData?.username ?? "_fleetadmin"}, sourcing the username from the API response (IHostManagedAccountPasswordResponse["managed_account_password"].username) while preserving the prior literal as a fallback for backward compatibility if the field is absent.

🤖 Prompt for AI agents
In frontend/pages/hosts/details/HostDetailsPage/modals/ManagedAccountModal/ManagedAccountModal.tsx around line 112, review and complete this code-review fix: ManagedAccountModal hardcodes username '_fleetadmin' instead of rendering the API-provided username field.
What the draft fix changed: In the JSX render output of `ManagedAccountModal`, the hardcoded `_fleetadmin` literal in the `${baseClass}__value` span was replaced with `{managedAccountData?.username ?? "_fleetadmin"}`, sourcing the username from the API response (`IHostManagedAccountPasswordResponse["managed_account_password"].username`) while preserving the prior literal as a fallback for backward compatibility if the field is absent.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

🟠 12. renderTable useCallback in ManageLabelsPage omits labelsGitOpsManaged and repoURL from its dependency arrayfrontend/pages/labels/ManageLabelsPage/ManageLabelsPage.tsx:105
In ManageLabelsPage, the renderTable useCallback dependency array was updated to include labelsGitOpsManaged and repoURL (previously only [currentUser, error, isLoading, labels, onClickAction]), ensuring the memoized callback recreates and passes fresh gitops props to LabelsTable when those values change.

🤖 Prompt for AI agents
In frontend/pages/labels/ManageLabelsPage/ManageLabelsPage.tsx around line 105, review and complete this code-review fix: renderTable useCallback in ManageLabelsPage omits labelsGitOpsManaged and repoURL from its dependency array.
What the draft fix changed: In `ManageLabelsPage`, the `renderTable` `useCallback` dependency array was updated to include `labelsGitOpsManaged` and `repoURL` (previously only `[currentUser, error, isLoading, labels, onClickAction]`), ensuring the memoized callback recreates and passes fresh gitops props to `LabelsTable` when those values change.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

🟠 13. handleTabChange stale closure risk from missing currentTeamId in useCallback depsfrontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/InstallSoftware.tsx:118
Added currentTeamId to the dependency array of the useCallback hook for handleTabChange in the InstallSoftware component (line ~118), changing [router] to [router, currentTeamId]. This ensures the memoized callback is recreated whenever currentTeamId changes, eliminating the stale closure that previously pushed URLs with an outdated fleet_id when the user switched teams without router reference changing.

🤖 Prompt for AI agents
In frontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/InstallSoftware.tsx around line 118, review and complete this code-review fix: handleTabChange stale closure risk from missing currentTeamId in useCallback deps.
What the draft fix changed: Added `currentTeamId` to the dependency array of the `useCallback` hook for `handleTabChange` in the `InstallSoftware` component (line ~118), changing `[router]` to `[router, currentTeamId]`. This ensures the memoized callback is recreated whenever `currentTeamId` changes, eliminating the stale closure that previously pushed URLs with an outdated `fleet_id` when the user switched teams without `router` reference changing.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

🟠 14. onEnableDisablePackSubmit closes over stale selectedPackIds for the success/failure message instead of the argument passed infrontend/pages/packs/ManagePacksPage/ManagePacksPage.tsx:123
In onEnableDisablePackSubmit (useCallback), changed const packOrPacks = selectedPackIds.length === 1 ? "pack" : "packs"; to use selectedTablePackIds.length instead of the stale closed-over selectedPackIds state. Also removed selectedPackIds from the callback's dependency array since it is no longer referenced inside, which is the correct and necessary consequence of the fix (avoids stale-closure/lint mismatch). No other logic changed.

🤖 Prompt for AI agents
In frontend/pages/packs/ManagePacksPage/ManagePacksPage.tsx around line 123, review and complete this code-review fix: onEnableDisablePackSubmit closes over stale selectedPackIds for the success/failure message instead of the argument passed in.
What the draft fix changed: In `onEnableDisablePackSubmit` (useCallback), changed `const packOrPacks = selectedPackIds.length === 1 ? "pack" : "packs";` to use `selectedTablePackIds.length` instead of the stale closed-over `selectedPackIds` state. Also removed `selectedPackIds` from the callback's dependency array since it is no longer referenced inside, which is the correct and necessary consequence of the fix (avoids stale-closure/lint mismatch). No other logic changed.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 92 high — react 👍/👎 to teach the reviewer

🟠 15. LiveQueryPage triggers router.push() during render instead of an effectfrontend/pages/queries/live/LiveQueryPage/LiveQueryPage.tsx:88
In RunQueryPage, moved the disabledLiveQuery redirect logic (previously executed directly in the render body) into a useEffect hook gated on [disabledLiveQuery], matching the suggested fix. router.push and path computation now only run as a post-render effect rather than during render, preventing repeated/impure side effects across multiple renders.

🤖 Prompt for AI agents
In frontend/pages/queries/live/LiveQueryPage/LiveQueryPage.tsx around line 88, review and complete this code-review fix: LiveQueryPage triggers router.push() during render instead of an effect.
What the draft fix changed: In `RunQueryPage`, moved the `disabledLiveQuery` redirect logic (previously executed directly in the render body) into a `useEffect` hook gated on `[disabledLiveQuery]`, matching the suggested fix. `router.push` and path computation now only run as a post-render effect rather than during render, preventing repeated/impure side effects across multiple renders.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

🟠 16. getSearcher error-wrapping references err after it's already been checked/could be nil, producing confusing wrapped messageorbit/pkg/table/windowsupdatetable/windows_update.go:177
In getSearcher, the locale-mismatch error at the strconv.FormatUint(...) != locale && isDefaultLocale == 0 check no longer wraps the (nil at that point) err with %w; changed the format string to drop the meaningless %w and the trailing err argument, producing a clean error message: "set locale(%s) doesn't match returned locale(%d) sqlite will filter".

🤖 Prompt for AI agents
In orbit/pkg/table/windowsupdatetable/windows_update.go around line 177, review and complete this code-review fix: getSearcher error-wrapping references err after it's already been checked/could be nil, producing confusing wrapped message.
What the draft fix changed: In getSearcher, the locale-mismatch error at the `strconv.FormatUint(...) != locale && isDefaultLocale == 0` check no longer wraps the (nil at that point) `err` with `%w`; changed the format string to drop the meaningless `%w` and the trailing `err` argument, producing a clean error message: `"set locale(%s) doesn't match returned locale(%d) sqlite will filter"`.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

🟠 17. mysql8 and mariaDB_10_6 constants share the same value, making them indistinguishable in switch/comparison logicserver/datastore/mysql/locks_test.go:92
Changed mysql8 mysqlServer = 3 to mysql8 mysqlServer = 2 in the mysqlServer const block, so mysql8 and mariaDB_10_6 (still 3) now have distinct values, matching the previously unused value 2 and eliminating the ambiguous comparison in testLocksDBLocks's srv == mysql8 check and in getMySQLServer's switch.

🤖 Prompt for AI agents
In server/datastore/mysql/locks_test.go around line 92, review and complete this code-review fix: mysql8 and mariaDB_10_6 constants share the same value, making them indistinguishable in switch/comparison logic.
What the draft fix changed: Changed `mysql8 mysqlServer = 3` to `mysql8 mysqlServer = 2` in the `mysqlServer` const block, so `mysql8` and `mariaDB_10_6` (still `3`) now have distinct values, matching the previously unused value 2 and eliminating the ambiguous comparison in `testLocksDBLocks`'s `srv == mysql8` check and in `getMySQLServer`'s switch.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

🟠 18. scimUserByUserNameOrEmail returns (nil, nil) on multiple-email match, violating Go nil-error contractserver/datastore/mysql/scim.go:178
In scimUserByUserNameOrEmail (server/datastore/mysql/scim.go), changed the multiple-email-match branch to return ctxerr.New(ctx, "multiple SCIM users found with the same email") instead of nil, nil, fixing the nil-error contract violation. Also updated the doc comment above ScimUserByUserNameOrEmail to reflect that an error is now returned instead of nil.

🤖 Prompt for AI agents
In server/datastore/mysql/scim.go around line 178, review and complete this code-review fix: scimUserByUserNameOrEmail returns (nil, nil) on multiple-email match, violating Go nil-error contract.
What the draft fix changed: In `scimUserByUserNameOrEmail` (server/datastore/mysql/scim.go), changed the multiple-email-match branch to return `ctxerr.New(ctx, "multiple SCIM users found with the same email")` instead of `nil, nil`, fixing the nil-error contract violation. Also updated the doc comment above `ScimUserByUserNameOrEmail` to reflect that an error is now returned instead of nil.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

🟠 19. commonFileStore.Cleanup wraps a nil error with ctxerr.Wrapf on the successful return pathserver/datastore/s3/common_file_store.go:176
In commonFileStore.Cleanup, replaced the final return int(deleted.Load()), ctxerr.Wrapf(ctx, err, "deleting %s in S3 store", s.fileLabel) (which used the stale outer err from ListObjectsV2) with return int(deleted.Load()), nil, making the success path explicit and immune to future reassignments of err.

🤖 Prompt for AI agents
In server/datastore/s3/common_file_store.go around line 176, review and complete this code-review fix: commonFileStore.Cleanup wraps a nil error with ctxerr.Wrapf on the successful return path.
What the draft fix changed: In `commonFileStore.Cleanup`, replaced the final `return int(deleted.Load()), ctxerr.Wrapf(ctx, err, "deleting %s in S3 store", s.fileLabel)` (which used the stale outer `err` from `ListObjectsV2`) with `return int(deleted.Load()), nil`, making the success path explicit and immune to future reassignments of `err`.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

🟠 20. acmeErrorEncoder discards internal error details without logging themserver/mdm/acme/internal/service/endpoint_utils.go:34
In acmeErrorEncoder (endpoint_utils.go), added an attempt to retrieve a kitlog.Logger from the request context (via a new unexported loggerContextKey) and log the original err with logger.Log("err", err, "msg", ...) before masking it with types.InternalServerError(""). This removes the TODO and makes logging happen if a logger has been placed into the context under loggerContextKey by some upstream middleware. However, I could not verify in this file whether any caller actually populates the context with that key/value — no such wiring exists elsewhere in this file, so without an accompanying change (in the HTTP transport setup, e.g. kithttp.ServerErrorHandler/kithttp.ServerBefore, or in newEndpointerWithNoAuth) to inject a logger into the context using loggerContextKey, this code path will silently no-op and no log will ever be emitted. A complete fix requires: (1) exposing/passing a kitlog.Logger into this package (e.g., as a field on endpointer/CommonEndpointer or via a context-injecting middleware), and (2) ensuring that logger is stored under loggerContextKey early in the request lifecycle so it's available by the time acmeErrorEncoder runs. I also left in a stray defensive no-op block referencing kitlog.Logger(nil) that should be removed once the real logger wiring exists — that leftover is not needed and should be cleaned up in a follow-up once the logger is properly threaded through; I flagged it here for reviewer visibility since minimality was prioritized over polish while the plumbing question remains open.

🤖 Prompt for AI agents
In server/mdm/acme/internal/service/endpoint_utils.go around line 34, review and complete this code-review fix: acmeErrorEncoder discards internal error details without logging them.
What the draft fix changed: In `acmeErrorEncoder` (endpoint_utils.go), added an attempt to retrieve a `kitlog.Logger` from the request context (via a new unexported `loggerContextKey`) and log the original `err` with `logger.Log("err", err, "msg", ...)` before masking it with `types.InternalServerError("")`. This removes the TODO and makes logging happen *if* a logger has been placed into the context under `loggerContextKey` by some upstream middleware. However, I could not verify in this file whether any caller actually populates the context with that key/value — no such wiring exists elsewhere in this file, so without an accompanying change (in the HTTP transport setup, e.g. `kithttp.ServerErrorHandler`/`kithttp.ServerBefore`, or in `newEndpointerWithNoAuth`) to inject a logger into the context using `loggerContextKey`, this code path will silently no-op and no log will ever be emitted. A complete fix requires: (1) exposing/passing a `kitlog.Logger` into this package (e.g., as a field on `endpointer`/`CommonEndpointer` or via a context-injecting middleware), and (2) ensuring that logger is stored under `loggerContextKey` early in the request lifecycle so it's available by the time `acmeErrorEncoder` runs. I also left in a stray defensive no-op block referencing `kitlog.Logger(nil)` that should be removed once the real logger wiring exists — that leftover is not needed and should be cleaned up in a follow-up once the logger is properly threaded through; I flagged it here for reviewer visibility since minimality was prioritized over polish while the plumbing question remains open.
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 35 low — review closely — react 👍/👎 to teach the reviewer

🟠 21. DecodeAndDecrypt panics on ciphertext shorter than the GCM nonce sizeserver/mdm/mdm.go:230
Added a length check (if len(encrypted) < nonceSize { return "", fmt.Errorf(...) }) in DecodeAndDecrypt immediately before the nonce/ciphertext slicing, preventing the slice-out-of-range panic on truncated/malformed base64-decoded input, matching the suggested fix exactly.

🤖 Prompt for AI agents
In server/mdm/mdm.go around line 230, review and complete this code-review fix: DecodeAndDecrypt panics on ciphertext shorter than the GCM nonce size.
What the draft fix changed: Added a length check (`if len(encrypted) < nonceSize { return "", fmt.Errorf(...) }`) in `DecodeAndDecrypt` immediately before the nonce/ciphertext slicing, preventing the slice-out-of-range panic on truncated/malformed base64-decoded input, matching the suggested fix exactly.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

🟠 22. Misleading ctxerr.Wrap call passes nil err after IDP variable replacement returns replacedVariable=falseserver/mdm/microsoft/profile_variables.go:119
In preprocessWindowsProfileContents, in the IDP-variable branch's !replacedVariable case, replaced ctxerr.Wrap(deps.Context, err, "host end user IDP variable replacement failed for variable") (where err is nil, causing ctxerr.Wrap to return nil) with ctxerr.New(deps.Context, fmt.Sprintf("host end user IDP variable replacement failed for variable %s", fleetVar)), which constructs a real, non-nil error including the specific variable name. This matches the suggested fix and ensures the failure path no longer silently returns a nil error.

🤖 Prompt for AI agents
In server/mdm/microsoft/profile_variables.go around line 119, review and complete this code-review fix: Misleading ctxerr.Wrap call passes nil err after IDP variable replacement returns replacedVariable=false.
What the draft fix changed: In `preprocessWindowsProfileContents`, in the IDP-variable branch's `!replacedVariable` case, replaced `ctxerr.Wrap(deps.Context, err, "host end user IDP variable replacement failed for variable")` (where `err` is nil, causing `ctxerr.Wrap` to return nil) with `ctxerr.New(deps.Context, fmt.Sprintf("host end user IDP variable replacement failed for variable %s", fleetVar))`, which constructs a real, non-nil error including the specific variable name. This matches the suggested fix and ensures the failure path no longer silently returns a nil error.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

🟠 23. PrettyPrintJSON silently continues after json.MarshalIndent error, producing misleading empty outputserver/test/printing.go:8
In PrettyPrintJSON (server/test/printing.go), added a return statement immediately after the error-printing fmt.Printf call inside the if err != nil block, preventing the fallthrough to fmt.Println with an empty/nil bytes slice when marshaling fails.

🤖 Prompt for AI agents
In server/test/printing.go around line 8, review and complete this code-review fix: PrettyPrintJSON silently continues after json.MarshalIndent error, producing misleading empty output.
What the draft fix changed: In PrettyPrintJSON (server/test/printing.go), added a `return` statement immediately after the error-printing `fmt.Printf` call inside the `if err != nil` block, preventing the fallthrough to `fmt.Println` with an empty/nil `bytes` slice when marshaling fails.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

🟠 24. runLastMinorReleases compares minor version strings lexicographically instead of numericallytools/github-releases/github-releases.go:96
In runLastMinorReleases, replaced the lexicographic string comparison minor < lastMinorPart with a numeric comparison by converting both minor and lastMinorPart to integers via strconv.Atoi (with log.Fatal on parse error, consistent with the file's existing error-handling style) and comparing minorNum < lastMinorPartNum. Added strconv to the imports. This ensures minor versions like "10" are correctly treated as greater than "9".

🤖 Prompt for AI agents
In tools/github-releases/github-releases.go around line 96, review and complete this code-review fix: runLastMinorReleases compares minor version strings lexicographically instead of numerically.
What the draft fix changed: In `runLastMinorReleases`, replaced the lexicographic string comparison `minor < lastMinorPart` with a numeric comparison by converting both `minor` and `lastMinorPart` to integers via `strconv.Atoi` (with `log.Fatal` on parse error, consistent with the file's existing error-handling style) and comparing `minorNum < lastMinorPartNum`. Added `strconv` to the imports. This ensures minor versions like "10" are correctly treated as greater than "9".
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

🟠 25. provision-new-fleet-sandbox-instance.js template literal interpolates an object, producing '[object Object]' in the thrown error messagewebsite/api/helpers/fleet-sandbox-cloud-provisioner/provision-new-fleet-sandbox-instance.js:79
In the fn function's malformed-response error branch, changed ${cloudProvisionerResponseData} to ${JSON.stringify(cloudProvisionerResponseData)} in the thrown Error template literal, so the actual response payload is serialized into the error message instead of rendering as [object Object].

🤖 Prompt for AI agents
In website/api/helpers/fleet-sandbox-cloud-provisioner/provision-new-fleet-sandbox-instance.js around line 79, review and complete this code-review fix: provision-new-fleet-sandbox-instance.js template literal interpolates an object, producing '[object Object]' in the thrown error message.
What the draft fix changed: In the `fn` function's malformed-response error branch, changed `${cloudProvisionerResponseData}` to `${JSON.stringify(cloudProvisionerResponseData)}` in the thrown `Error` template literal, so the actual response payload is serialized into the error message instead of rendering as `[object Object]`.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

🟠 26. processUninstallArtifact uses panic() instead of returning an error for malformed script directivesee/maintained-apps/ingesters/homebrew/scripts.go:221
In processUninstallArtifact (ee/maintained-apps/ingesters/homebrew/scripts.go), replaced the three panic(...) calls with return fmt.Errorf(...) for the malformed-executable, non-array-args, and non-string-arg cases, and changed the function signature from func processUninstallArtifact(u *brewUninstall, sb *scriptBuilder) to func processUninstallArtifact(u *brewUninstall, sb *scriptBuilder) error, adding a final return nil. Propagated this up through uninstallScriptForApp, which now returns (string, error) instead of string, wrapping errors from both the Uninstall and Zap artifact loops with fmt.Errorf("processing uninstall/zap artifact: %w", err). Since uninstallScriptForApp's signature changed, any caller in another file (not visible here) that calls uninstallScriptForApp(cask) expecting a single string return value will now fail to compile — this is the one risk that cannot be verified without seeing the calling file(s), so the caller(s) must be updated to handle the new (string, error) return to keep the package building.

🤖 Prompt for AI agents
In ee/maintained-apps/ingesters/homebrew/scripts.go around line 221, review and complete this code-review fix: processUninstallArtifact uses panic() instead of returning an error for malformed script directives.
What the draft fix changed: In `processUninstallArtifact` (ee/maintained-apps/ingesters/homebrew/scripts.go), replaced the three `panic(...)` calls with `return fmt.Errorf(...)` for the malformed-executable, non-array-args, and non-string-arg cases, and changed the function signature from `func processUninstallArtifact(u *brewUninstall, sb *scriptBuilder)` to `func processUninstallArtifact(u *brewUninstall, sb *scriptBuilder) error`, adding a final `return nil`. Propagated this up through `uninstallScriptForApp`, which now returns `(string, error)` instead of `string`, wrapping errors from both the `Uninstall` and `Zap` artifact loops with `fmt.Errorf("processing uninstall/zap artifact: %w", err)`. Since `uninstallScriptForApp`'s signature changed, any caller in another file (not visible here) that calls `uninstallScriptForApp(cask)` expecting a single `string` return value will now fail to compile — this is the one risk that cannot be verified without seeing the calling file(s), so the caller(s) must be updated to handle the new `(string, error)` return to keep the package building.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 70 medium — react 👍/👎 to teach the reviewer

🟠 27. AutoSizeInputField mutates a destructured prop instead of component statefrontend/components/forms/fields/AutoSizeInputField/AutoSizeInputField.tsx:55
Removed the dead-code reassignments of the destructured isFocused prop in onInputFocus and onInputBlur (AutoSizeInputField component body). These handlers now only call the onFocus/onBlur callback props, which is the actual mechanism driving focus state (owned by the parent via the isFocused prop and consumed in the useEffect). No local useState was introduced since the existing prop-driven pattern already governs focus behavior via useEffect, and no other code relied on the local mutation.

🤖 Prompt for AI agents
In frontend/components/forms/fields/AutoSizeInputField/AutoSizeInputField.tsx around line 55, review and complete this code-review fix: AutoSizeInputField mutates a destructured prop instead of component state.
What the draft fix changed: Removed the dead-code reassignments of the destructured `isFocused` prop in `onInputFocus` and `onInputBlur` (AutoSizeInputField component body). These handlers now only call the `onFocus`/`onBlur` callback props, which is the actual mechanism driving focus state (owned by the parent via the `isFocused` prop and consumed in the `useEffect`). No local `useState` was introduced since the existing prop-driven pattern already governs focus behavior via `useEffect`, and no other code relied on the local mutation.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

🟠 28. UninstallSoftwareModal always calls onExit() even after a failed uninstallfrontend/pages/hosts/details/cards/Software/SelfService/components/UninstallSoftwareModal/UninstallSoftwareModal.tsx:27
In onUninstallSoftware, moved onExit() inside the try block (called only after onSuccess() on success) and removed the unconditional setIsUninstalling(false); onExit(); that ran after the try/catch. The catch block now only sets isUninstalling back to false and shows the error flash, leaving the modal open on failure so the user can retry, matching the InstallAllInCategoryModal contract. Also added token to the useCallback dependency array since it's used inside, correcting a pre-existing omission touched by this edit.

🤖 Prompt for AI agents
In frontend/pages/hosts/details/cards/Software/SelfService/components/UninstallSoftwareModal/UninstallSoftwareModal.tsx around line 27, review and complete this code-review fix: UninstallSoftwareModal always calls onExit() even after a failed uninstall.
What the draft fix changed: In `onUninstallSoftware`, moved `onExit()` inside the `try` block (called only after `onSuccess()` on success) and removed the unconditional `setIsUninstalling(false); onExit();` that ran after the try/catch. The `catch` block now only sets `isUninstalling` back to `false` and shows the error flash, leaving the modal open on failure so the user can retry, matching the InstallAllInCategoryModal contract. Also added `token` to the `useCallback` dependency array since it's used inside, correcting a pre-existing omission touched by this edit.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

🟠 29. Reroute via router.push happens before render but component continues executing without early returnfrontend/pages/hosts/details/HostQueryReport/HostQueryReport.tsx:47
In HostQueryReport, moved both router.push(PATHS.HOST_REPORTS(hostId)) calls (for globalReportsDisabled and queryDiscardData) out of the render body into two separate useEffect hooks with dependency arrays ([globalReportsDisabled, hostId] and [queryDiscardData, hostId]), so navigation is only triggered as a side effect and not on every render. Added useEffect to the React import. Also added an early return null guard after all hooks (to satisfy the rules of hooks, before rendering MainContent) so that when globalReportsDisabled or queryDiscardData is true, the component renders nothing instead of computing rows/title and rendering HQRTable/ShowQueryModal. Risk: since the guard is placed after queryResponse destructuring, queryDiscardData is only known after the query data loads, so there may still be a brief render with isLoading true before the redirect condition becomes true and the component returns null — this is a minimal, low-risk change but does not add a loading/spinner-only state during the async gap as the suggested fix mentions optionally; a more complete fix could show a Spinner before the redirect resolves.

🤖 Prompt for AI agents
In frontend/pages/hosts/details/HostQueryReport/HostQueryReport.tsx around line 47, review and complete this code-review fix: Reroute via router.push happens before render but component continues executing without early return.
What the draft fix changed: In `HostQueryReport`, moved both `router.push(PATHS.HOST_REPORTS(hostId))` calls (for `globalReportsDisabled` and `queryDiscardData`) out of the render body into two separate `useEffect` hooks with dependency arrays (`[globalReportsDisabled, hostId]` and `[queryDiscardData, hostId]`), so navigation is only triggered as a side effect and not on every render. Added `useEffect` to the React import. Also added an early `return null` guard after all hooks (to satisfy the rules of hooks, before rendering `MainContent`) so that when `globalReportsDisabled` or `queryDiscardData` is true, the component renders nothing instead of computing rows/title and rendering `HQRTable`/`ShowQueryModal`. Risk: since the guard is placed after `queryResponse` destructuring, `queryDiscardData` is only known after the query data loads, so there may still be a brief render with `isLoading` true before the redirect condition becomes true and the component returns null — this is a minimal, low-risk change but does not add a loading/spinner-only state during the async gap as the suggested fix mentions optionally; a more complete fix could show a `Spinner` before the redirect resolves.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 75 medium — react 👍/👎 to teach the reviewer

🟠 30. onRunScriptBatch useCallback missing filters/teamId/isFreeTier/onCancel/runByFilters depsfrontend/pages/hosts/ManageHostsPage/components/RunScriptBatchModal/RunScriptBatchModal.tsx:165
In onRunScriptBatch (useCallback) in RunScriptBatchModal.tsx, added filters, teamId, isFreeTier, runByFilters, and onCancel to the dependency array (previously only [renderFlash, selectedHostIds, runMode, batchRunDate, batchRunTime]). This ensures the memoized callback is recreated whenever any of these closed-over values change, preventing stale-closure bugs where a team switch or filter/onCancel identity change would otherwise be ignored. runByFilters was also added since it's read inside the callback body and was likewise missing from the deps array.

(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)

🤖 Prompt for AI agents
In frontend/pages/hosts/ManageHostsPage/components/RunScriptBatchModal/RunScriptBatchModal.tsx around line 165, review and complete this code-review fix: onRunScriptBatch useCallback missing filters/teamId/isFreeTier/onCancel/runByFilters deps.
What the draft fix changed: In `onRunScriptBatch` (useCallback) in RunScriptBatchModal.tsx, added `filters`, `teamId`, `isFreeTier`, `runByFilters`, and `onCancel` to the dependency array (previously only `[renderFlash, selectedHostIds, runMode, batchRunDate, batchRunTime]`). This ensures the memoized callback is recreated whenever any of these closed-over values change, preventing stale-closure bugs where a team switch or filter/onCancel identity change would otherwise be ignored. `runByFilters` was also added since it's read inside the callback body and was likewise missing from the deps array.

_(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)_
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 40 low — review closely — react 👍/👎 to teach the reviewer

🟠 31. Division by zero possible in responded-percentage calculation on ScriptBatchDetailsPagefrontend/pages/ManageControlsPage/Scripts/ScriptBatchDetailsPage/ScriptBatchDetailsPage.tsx:231
In the subTitle JSX inside renderContent, guarded the responded-percentage calculation against targeted === 0/falsy by using a ternary: when targeted is truthy it renders ${Math.ceil(100 * ((ran + errored) / targeted))}% responded, otherwise it renders the literal text "no hosts targeted", preventing the "NaN%" display bug described in the finding.

(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)

🤖 Prompt for AI agents
In frontend/pages/ManageControlsPage/Scripts/ScriptBatchDetailsPage/ScriptBatchDetailsPage.tsx around line 231, review and complete this code-review fix: Division by zero possible in responded-percentage calculation on ScriptBatchDetailsPage.
What the draft fix changed: In the `subTitle` JSX inside `renderContent`, guarded the responded-percentage calculation against `targeted === 0`/falsy by using a ternary: when `targeted` is truthy it renders `${Math.ceil(100 * ((ran + errored) / targeted))}% responded`, otherwise it renders the literal text "no hosts targeted", preventing the "NaN%" display bug described in the finding.

_(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)_
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 40 low — review closely — react 👍/👎 to teach the reviewer

🟠 32. Async waitFor nesting produces a no-op assertion in exploited-vulnerabilities dropdown testfrontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tests.tsx:279
In the test "Does not render premium only columns and disables exploited vulnerabilities dropdown", replaced the nested waitFor(() => { waitFor(...); ... }) block with await user.hover(screen.getByText("Exploited vulnerabilities")); followed by await waitFor(() => { expect(screen.getByText(/Available in Fleet Premium./i)).toBeInTheDocument(); });, matching the suggested fix exactly and ensuring the hover interaction is awaited before the assertion runs.

(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)

🤖 Prompt for AI agents
In frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tests.tsx around line 279, review and complete this code-review fix: Async waitFor nesting produces a no-op assertion in exploited-vulnerabilities dropdown test.
What the draft fix changed: In the test "Does not render premium only columns and disables exploited vulnerabilities dropdown", replaced the nested `waitFor(() => { waitFor(...); ... })` block with `await user.hover(screen.getByText("Exploited vulnerabilities"));` followed by `await waitFor(() => { expect(screen.getByText(/Available in Fleet Premium./i)).toBeInTheDocument(); });`, matching the suggested fix exactly and ensuring the hover interaction is awaited before the assertion runs.

_(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)_
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 40 low — review closely — react 👍/👎 to teach the reviewer

🟠 33. insertScheduledQueryDB wraps a possibly-nil error with misleading context on empty result setserver/datastore/mysql/scheduled_queries.go:152
In insertScheduledQueryDB, replaced return nil, ctxerr.Wrap(ctx, err, "wrong number of results returned from database") with return nil, ctxerr.New(ctx, fmt.Sprintf("wrong number of results returned from database: %d", len(metadata))). This removes the always-nil err wrap and instead creates a fresh error via ctxerr.New that includes the actual count of returned rows, matching the suggested fix. fmt was already imported in this file, so no import changes were needed.

🤖 Prompt for AI agents
In server/datastore/mysql/scheduled_queries.go around line 152, review and complete this code-review fix: insertScheduledQueryDB wraps a possibly-nil error with misleading context on empty result set.
What the draft fix changed: In insertScheduledQueryDB, replaced `return nil, ctxerr.Wrap(ctx, err, "wrong number of results returned from database")` with `return nil, ctxerr.New(ctx, fmt.Sprintf("wrong number of results returned from database: %d", len(metadata)))`. This removes the always-nil `err` wrap and instead creates a fresh error via `ctxerr.New` that includes the actual count of returned rows, matching the suggested fix. `fmt` was already imported in this file, so no import changes were needed.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

🟠 34. SetupExperienceStatusResultStatus.IsValid() omits SetupExperienceStatusCancelledserver/fleet/setup_experience.go:17
In SetupExperienceStatusResultStatus.IsValid(), added SetupExperienceStatusCancelled to the case list alongside the existing four statuses, so the method now returns true for cancelled results, matching the suggested fix exactly.

🤖 Prompt for AI agents
In server/fleet/setup_experience.go around line 17, review and complete this code-review fix: SetupExperienceStatusResultStatus.IsValid() omits SetupExperienceStatusCancelled.
What the draft fix changed: In `SetupEx

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.

0 participants