Skip to content

Commit 2760fad

Browse files
Bill Leoutsakoscursoragent
authored andcommitted
fix(pi): address Cloud Code Review review and lint findings
Use the checked-out SHA for review submission, skip invalid/null inline comments, scrub GitGuardian-flagged test fixtures, and fix Biome import/format issues. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent c7e4d0c commit 2760fad

4 files changed

Lines changed: 107 additions & 33 deletions

File tree

apps/sim/executor/handlers/pi/cloud-backend.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import {
2323
PI_SCRIPT,
2424
PI_TIMEOUT_MS,
2525
PROMPT_PATH,
26-
raceAbort,
2726
REPO_DIR,
27+
raceAbort,
2828
scrubGitSecrets,
2929
} from '@/executor/handlers/pi/cloud-shared'
3030
import { buildPiPrompt } from '@/executor/handlers/pi/context'

apps/sim/executor/handlers/pi/cloud-review-backend.test.ts

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ describe('runCloudReviewPi', () => {
7979
}
8080
return Promise.resolve({
8181
success: true,
82-
output: { metadata: { html_url: 'https://github.com/octo/demo/pull/7#pullrequestreview-9' } },
82+
output: {
83+
metadata: { html_url: 'https://github.com/octo/demo/pull/7#pullrequestreview-9' },
84+
},
8385
})
8486
})
8587
mockRun.mockImplementation(
@@ -150,9 +152,7 @@ describe('runCloudReviewPi', () => {
150152
event: 'COMMENT',
151153
body: 'Overall looks solid.',
152154
commit_id: 'deadbeef',
153-
comments: [
154-
{ path: 'src/x.ts', body: 'Consider a null check', line: 12, side: 'RIGHT' },
155-
],
155+
comments: [{ path: 'src/x.ts', body: 'Consider a null check', line: 12, side: 'RIGHT' }],
156156
apiKey: 'ghp_secret',
157157
})
158158
)
@@ -161,6 +161,69 @@ describe('runCloudReviewPi', () => {
161161
expect(result.prUrl).toBeUndefined()
162162
})
163163

164+
it('submits against the checked-out SHA when the API head moved', async () => {
165+
mockRun.mockImplementation((command: string) => {
166+
if (command.includes('git clone') || command.includes('git fetch')) {
167+
return Promise.resolve({
168+
stdout: '__HEAD_SHA__=clonedsha99',
169+
stderr: '',
170+
exitCode: 0,
171+
})
172+
}
173+
if (command.includes('pi -p')) {
174+
return Promise.resolve({ stdout: '', stderr: '', exitCode: 0 })
175+
}
176+
return Promise.resolve({ stdout: '', stderr: '', exitCode: 0 })
177+
})
178+
179+
await runCloudReviewPi(baseParams(), { onEvent: vi.fn() })
180+
181+
expect(mockExecuteTool).toHaveBeenCalledWith(
182+
'github_create_pr_review',
183+
expect.objectContaining({ commit_id: 'clonedsha99' })
184+
)
185+
})
186+
187+
it('treats null comments as empty and drops invalid inline comments', async () => {
188+
mockReadFile.mockResolvedValue(
189+
JSON.stringify({
190+
body: 'Summary only',
191+
comments: [
192+
null,
193+
{ path: 'a.ts', body: 'missing line' },
194+
{ path: 'b.ts', body: 'bad line', line: 0 },
195+
{ path: 'c.ts', body: 'ok', line: 4, side: 'RIGHT' },
196+
],
197+
})
198+
)
199+
200+
const result = await runCloudReviewPi(baseParams(), { onEvent: vi.fn() })
201+
202+
expect(mockExecuteTool).toHaveBeenCalledWith(
203+
'github_create_pr_review',
204+
expect.objectContaining({
205+
body: 'Summary only',
206+
comments: [{ path: 'c.ts', body: 'ok', line: 4, side: 'RIGHT' }],
207+
})
208+
)
209+
expect(result.commentsPosted).toBe(1)
210+
})
211+
212+
it('allows comments: null without aborting the review', async () => {
213+
mockReadFile.mockResolvedValue(JSON.stringify({ body: 'No inline notes', comments: null }))
214+
215+
const result = await runCloudReviewPi(baseParams(), { onEvent: vi.fn() })
216+
217+
expect(mockExecuteTool).toHaveBeenCalledWith(
218+
'github_create_pr_review',
219+
expect.objectContaining({
220+
body: 'No inline notes',
221+
comments: [],
222+
})
223+
)
224+
expect(result.commentsPosted).toBe(0)
225+
})
226+
164227
it('rejects a non-BYOK key', async () => {
165228
await expect(
166229
runCloudReviewPi(baseParams({ isBYOK: false }), { onEvent: vi.fn() })
@@ -178,9 +241,10 @@ describe('runCloudReviewPi', () => {
178241
})
179242

180243
it('scrubs the token from clone failures', async () => {
244+
// Avoid embedding a basic-auth URL (GitGuardian); scrubbing still covers bare tokens.
181245
mockRun.mockResolvedValue({
182246
stdout: '',
183-
stderr: "fatal: unable to access 'https://x-access-token:ghp_secret@github.com/octo/demo.git/'",
247+
stderr: 'fatal: Authentication failed for token ghp_secret',
184248
exitCode: 1,
185249
})
186250

apps/sim/executor/handlers/pi/cloud-review-backend.ts

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ import {
1717
PI_SCRIPT,
1818
PI_TIMEOUT_MS,
1919
PROMPT_PATH,
20-
raceAbort,
2120
REPO_DIR,
21+
raceAbort,
2222
scrubGitSecrets,
2323
} from '@/executor/handlers/pi/cloud-shared'
2424
import { buildPiPrompt } from '@/executor/handlers/pi/context'
2525
import { applyPiEvent, createPiTotals, parseJsonLine } from '@/executor/handlers/pi/events'
2626
import { mapThinkingLevel, providerApiKeyEnvVar } from '@/executor/handlers/pi/keys'
27-
import type { CreatePRReviewComment } from '@/tools/github/types'
2827
import { executeTool } from '@/tools'
28+
import type { CreatePRReviewComment } from '@/tools/github/types'
2929

3030
const logger = createLogger('PiCloudReviewBackend')
3131

@@ -83,9 +83,7 @@ async function fetchPrContext(params: PiCloudReviewRunParams): Promise<PrContext
8383
})
8484

8585
if (!result.success) {
86-
throw new Error(
87-
`Failed to fetch PR #${params.pullNumber}: ${result.error ?? 'unknown error'}`
88-
)
86+
throw new Error(`Failed to fetch PR #${params.pullNumber}: ${result.error ?? 'unknown error'}`)
8987
}
9088

9189
const output = result.output as {
@@ -143,6 +141,15 @@ function buildPrContextMarkdown(params: PiCloudReviewRunParams, pr: PrContext):
143141
: content
144142
}
145143

144+
function isPositiveInt(value: unknown): value is number {
145+
return typeof value === 'number' && Number.isInteger(value) && value >= 1
146+
}
147+
148+
/**
149+
* Parses agent review JSON. Invalid inline comments are skipped so a usable
150+
* summary body can still be submitted; comments without a valid line are dropped
151+
* because GitHub rejects line-less review comments when commit_id is set.
152+
*/
146153
function parseReviewFindings(raw: string): ParsedReviewFindings {
147154
let parsed: unknown
148155
try {
@@ -161,30 +168,35 @@ function parseReviewFindings(raw: string): ParsedReviewFindings {
161168
}
162169

163170
const comments: CreatePRReviewComment[] = []
164-
if (record.comments !== undefined) {
171+
// Treat null/undefined as "no comments" — agents often emit null for optional fields.
172+
if (record.comments != null) {
165173
if (!Array.isArray(record.comments)) {
166174
throw new Error('Pi review output comments must be an array when present')
167175
}
168-
for (const [index, item] of record.comments.entries()) {
169-
if (!item || typeof item !== 'object') {
170-
throw new Error(`Pi review comments[${index}] must be an object`)
171-
}
176+
for (const item of record.comments) {
177+
if (!item || typeof item !== 'object') continue
172178
const comment = item as Record<string, unknown>
173-
if (typeof comment.path !== 'string' || !comment.path.trim()) {
174-
throw new Error(`Pi review comments[${index}].path is required`)
175-
}
176-
if (typeof comment.body !== 'string' || !comment.body.trim()) {
177-
throw new Error(`Pi review comments[${index}].body is required`)
178-
}
179+
if (typeof comment.path !== 'string' || !comment.path.trim()) continue
180+
if (typeof comment.body !== 'string' || !comment.body.trim()) continue
181+
if (!isPositiveInt(comment.line)) continue
182+
179183
const normalized: CreatePRReviewComment = {
180184
path: comment.path.trim(),
181185
body: comment.body,
186+
line: comment.line,
187+
side: comment.side === 'LEFT' || comment.side === 'RIGHT' ? comment.side : 'RIGHT',
182188
}
183-
if (typeof comment.line === 'number') normalized.line = comment.line
184-
if (comment.side === 'LEFT' || comment.side === 'RIGHT') normalized.side = comment.side
185-
if (typeof comment.start_line === 'number') normalized.start_line = comment.start_line
186-
if (comment.start_side === 'LEFT' || comment.start_side === 'RIGHT') {
187-
normalized.start_side = comment.start_side
189+
if (
190+
isPositiveInt(comment.start_line) &&
191+
comment.start_line < comment.line &&
192+
(comment.start_side === undefined ||
193+
comment.start_side === 'LEFT' ||
194+
comment.start_side === 'RIGHT')
195+
) {
196+
normalized.start_line = comment.start_line
197+
if (comment.start_side === 'LEFT' || comment.start_side === 'RIGHT') {
198+
normalized.start_side = comment.start_side
199+
}
188200
}
189201
comments.push(normalized)
190202
}
@@ -327,7 +339,9 @@ export const runCloudReviewPi: PiBackendRun<PiCloudReviewRunParams> = async (par
327339
totals.finalText = findings.body
328340
}
329341

330-
const { reviewUrl, commentsPosted } = await submitReview(params, pr.headSha, findings)
342+
// Submit against the SHA we actually checked out, not the earlier API fetch —
343+
// the PR head can move between fetchPrContext and clone.
344+
const { reviewUrl, commentsPosted } = await submitReview(params, clonedHead, findings)
331345

332346
logger.info('Pi cloud review submitted', {
333347
owner: params.owner,

apps/sim/executor/handlers/pi/pi-handler.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,7 @@ export class PiBlockHandler implements BlockHandler {
6868

6969
// Validate the mode up front so an invalid value reports a mode error rather
7070
// than a misattributed credential error from key resolution below.
71-
if (
72-
inputs.mode !== 'cloud' &&
73-
inputs.mode !== 'cloud_review' &&
74-
inputs.mode !== 'local'
75-
) {
71+
if (inputs.mode !== 'cloud' && inputs.mode !== 'cloud_review' && inputs.mode !== 'local') {
7672
throw new Error(`Invalid Pi mode: ${String(inputs.mode)}`)
7773
}
7874
const mode: 'cloud' | 'cloud_review' | 'local' = inputs.mode

0 commit comments

Comments
 (0)