@@ -243,24 +243,35 @@ function runSupervisedProcess(command, args, options = {}) {
243243 let cancelled = false ;
244244 let timeoutReason = null ;
245245 let terminationRequested = false ;
246+ let rejectedEvent = null ;
247+ let requestTermination = ( ) => { } ;
246248 let lineBuffer = "" ;
247249 child . stdout . setEncoding ( "utf8" ) ;
248250 child . stderr . setEncoding ( "utf8" ) ;
249251 const recordChunk = ( stream , chunk ) => {
250252 lastActivityAt = Date . now ( ) ;
251253 if ( logPath !== "" ) fs . appendFileSync ( logPath , `[${ stream } ] ${ chunk } ` ) ;
252254 } ;
253- const recordEvents = ( chunk ) => {
255+ const recordEvents = ( chunk , flush = false ) => {
254256 lineBuffer += chunk ;
255257 const lines = lineBuffer . split ( / \r ? \n / ) ;
256- lineBuffer = lines . pop ( ) || "" ;
258+ if ( flush ) lineBuffer = "" ;
259+ else lineBuffer = lines . pop ( ) || "" ;
257260 for ( const line of lines ) {
258261 if ( line . trim ( ) === "" ) continue ;
259262 try {
260263 const event = JSON . parse ( line ) ;
261264 lastEventAt = Date . now ( ) ;
262265 lastEventType = String ( event . type || event . event || event . subtype || "json" ) ;
263266 options . onEvent ?. ( event ) ;
267+ const rejection = options . rejectEvent ?. ( event ) ;
268+ if ( rejectedEvent === null && rejection ) {
269+ rejectedEvent = {
270+ type : lastEventType ,
271+ reason : String ( rejection ) ,
272+ } ;
273+ requestTermination ( ) ;
274+ }
264275 } catch {
265276 // Human-readable diagnostics remain in the local log and terminal result.
266277 }
@@ -271,6 +282,7 @@ function runSupervisedProcess(command, args, options = {}) {
271282 recordChunk ( "stdout" , chunk ) ;
272283 recordEvents ( chunk ) ;
273284 } ) ;
285+ child . stdout . on ( "end" , ( ) => recordEvents ( "" , true ) ) ;
274286 child . stderr . on ( "data" , ( chunk ) => {
275287 stderr += chunk ;
276288 recordChunk ( "stderr" , chunk ) ;
@@ -294,9 +306,12 @@ function runSupervisedProcess(command, args, options = {}) {
294306 timedOut,
295307 cancelled,
296308 timeout_reason : timeoutReason ,
297- terminal_state : timedOut
298- ? REVIEW_STATES . TIMED_OUT
299- : cancelled
309+ rejected_event : rejectedEvent ,
310+ terminal_state : rejectedEvent !== null
311+ ? REVIEW_STATES . INVALID_VERDICT
312+ : timedOut
313+ ? REVIEW_STATES . TIMED_OUT
314+ : cancelled
300315 ? REVIEW_STATES . CANCELLED
301316 : result . error
302317 ? REVIEW_STATES . INTERNAL_RUNNER_FAILURE
@@ -311,7 +326,7 @@ function runSupervisedProcess(command, args, options = {}) {
311326 log_path : logPath || null ,
312327 } ) ;
313328 } ;
314- const requestTermination = ( ) => {
329+ requestTermination = ( ) => {
315330 if ( terminationRequested ) return ;
316331 terminationRequested = true ;
317332 terminateProcessTree ( child , "SIGTERM" ) ;
@@ -392,6 +407,7 @@ function runtimeAttemptMetadata(attempt) {
392407 timeout_reason : attempt . timeout_reason || null ,
393408 last_event_at : attempt . last_event_at || null ,
394409 last_event_type : attempt . last_event_type || "none" ,
410+ rejected_event : attempt . rejected_event || null ,
395411 log_path : attempt . log_path || null ,
396412 retry_reason : attempt . retry_reason || null ,
397413 } ;
@@ -1127,11 +1143,23 @@ function actualClaudeModel(parsed, envelope) {
11271143 return candidates . find ( ( candidate ) => typeof candidate === "string" && candidate !== "" ) || "" ;
11281144}
11291145
1146+ function rejectClaudeReviewerToolUse ( event ) {
1147+ const events = Array . isArray ( event ) ? event : [ event ] ;
1148+ const blocks = events . flatMap ( ( entry ) => [
1149+ entry ,
1150+ entry ?. content_block ,
1151+ ...( Array . isArray ( entry ?. message ?. content ) ? entry . message . content : [ ] ) ,
1152+ ] ) . filter ( ( block ) => block && typeof block === "object" ) ;
1153+ const toolBlock = blocks . find ( ( block ) => / (?: ^ | _ ) t o o l _ u s e $ / . test ( String ( block . type || "" ) ) ) ;
1154+ if ( ! toolBlock ) return "" ;
1155+ return `forbidden reviewer tool use: ${ toolBlock . name || "unknown" } ` ;
1156+ }
1157+
11301158async function runClaudeReviewer ( prompt , temporaryDirectory , signal , reviewer , route , fallbackReason = null , logDirectory , phase = "initial" ) {
11311159 const launch = claudeLaunch ( ) ;
11321160 const args = [
11331161 "-p" , "--model" , reviewer . model , "--effort" , reviewer . effort , "--safe-mode" , "--max-turns" , "2" ,
1134- "--setting-sources" , "user" , "--tools" , "" , "--disable-slash-commands" ,
1162+ "--setting-sources" , "user" , "--tools" , "" , "--disallowedTools" , "advisor" , "-- disable-slash-commands",
11351163 "--no-session-persistence" , "--mcp-config" , '{"mcpServers":{}}' , "--strict-mcp-config" ,
11361164 "--json-schema" , JSON . stringify ( REVIEW_SCHEMA ) , "--output-format" , "stream-json" , "--verbose" ,
11371165 ] ;
@@ -1146,11 +1174,19 @@ async function runClaudeReviewer(prompt, temporaryDirectory, signal, reviewer, r
11461174 heartbeatInterval : configuredDuration ( "CODEX_SDLC_REVIEW_HEARTBEAT_MS" , 30 * 1000 ) ,
11471175 killGrace : configuredDuration ( "CODEX_SDLC_REVIEW_KILL_GRACE_MS" , 2000 ) ,
11481176 structuredEventsRequired : true ,
1177+ rejectEvent : rejectClaudeReviewerToolUse ,
11491178 signal,
11501179 label : `${ reviewer . label } ${ phase } ` ,
11511180 logPath : path . join ( logDirectory , `cross-model-${ phase } -attempt-${ attempt } .log` ) ,
11521181 windowsVerbatimArguments : prepared . windowsVerbatimArguments ,
11531182 } ) ;
1183+ if ( result . rejected_event ) {
1184+ return {
1185+ ...result ,
1186+ retryable : false ,
1187+ terminal_state : REVIEW_STATES . INVALID_VERDICT ,
1188+ } ;
1189+ }
11541190 const availabilityReason = claudeAvailabilityReason ( result ) ;
11551191 if ( availabilityReason !== "" ) {
11561192 return {
@@ -1208,7 +1244,7 @@ async function confirmClaudeReviewerUnavailable(temporaryDirectory, signal, revi
12081244 const launch = claudeLaunch ( ) ;
12091245 const args = [
12101246 "-p" , "--model" , reviewer . model , "--effort" , reviewer . effort , "--safe-mode" , "--max-turns" , "1" ,
1211- "--setting-sources" , "user" , "--tools" , "" , "--disable-slash-commands" ,
1247+ "--setting-sources" , "user" , "--tools" , "" , "--disallowedTools" , "advisor" , "-- disable-slash-commands",
12121248 "--no-session-persistence" , "--mcp-config" , '{"mcpServers":{}}' , "--strict-mcp-config" ,
12131249 "--output-format" , "stream-json" , "--verbose" ,
12141250 ] ;
@@ -1222,12 +1258,13 @@ async function confirmClaudeReviewerUnavailable(temporaryDirectory, signal, revi
12221258 heartbeatInterval : configuredDuration ( "CODEX_SDLC_REVIEW_HEARTBEAT_MS" , 30 * 1000 ) ,
12231259 killGrace : configuredDuration ( "CODEX_SDLC_REVIEW_KILL_GRACE_MS" , 2000 ) ,
12241260 structuredEventsRequired : true ,
1261+ rejectEvent : rejectClaudeReviewerToolUse ,
12251262 signal,
12261263 label : `${ reviewer . label } availability probe` ,
12271264 logPath : path . join ( logDirectory , "cross-model-availability-probe.log" ) ,
12281265 windowsVerbatimArguments : prepared . windowsVerbatimArguments ,
12291266 } ) ;
1230- if ( result . error || result . timedOut || result . cancelled ) return false ;
1267+ if ( result . rejected_event || result . error || result . timedOut || result . cancelled ) return false ;
12311268 return claudeAvailabilityReason ( result ) === suspectedReason ;
12321269}
12331270
@@ -1471,6 +1508,7 @@ module.exports = {
14711508 REVIEW_STATES ,
14721509 buildWindowsCommandLine,
14731510 classifyReviewFailure,
1511+ rejectClaudeReviewerToolUse,
14741512 runSupervisedProcess,
14751513 runWithInfrastructureRetry,
14761514} ;
0 commit comments