@@ -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'
2424import { buildPiPrompt } from '@/executor/handlers/pi/context'
2525import { applyPiEvent , createPiTotals , parseJsonLine } from '@/executor/handlers/pi/events'
2626import { mapThinkingLevel , providerApiKeyEnvVar } from '@/executor/handlers/pi/keys'
27- import type { CreatePRReviewComment } from '@/tools/github/types'
2827import { executeTool } from '@/tools'
28+ import type { CreatePRReviewComment } from '@/tools/github/types'
2929
3030const 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+ */
146153function 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 ,
0 commit comments