From b8075be92dcfc20de683d0074a426b7c52ca650b Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Sun, 29 Mar 2026 21:20:16 +0900 Subject: [PATCH] fix: check reviewer verdict on failed execution to prevent infinite loop --- src/paired-execution-context.ts | 16 +++++++++++++++- src/session-commands.test.ts | 2 -- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/paired-execution-context.ts b/src/paired-execution-context.ts index 3e19647..cb0e583 100644 --- a/src/paired-execution-context.ts +++ b/src/paired-execution-context.ts @@ -257,8 +257,22 @@ export function completePairedExecutionContext(args: { const task = getPairedTaskById(taskId); if (!task) return; - // On failure, reset task to active so the flow isn't stuck + // On failure: for reviewers, still check verdict from summary — output may + // have been delivered even though the executor classified it as failed + // (e.g. intermediate buffer → null result). This prevents infinite loops. if (status !== 'succeeded') { + if (role === 'reviewer' && args.summary) { + const verdict = classifyReviewerVerdict(args.summary); + if (verdict === 'done' || verdict === 'blocked' || verdict === 'needs_context') { + const now = new Date().toISOString(); + updatePairedTask(taskId, { status: verdict === 'done' ? 'merge_ready' : 'completed', updated_at: now }); + logger.info( + { taskId, verdict, summary: args.summary?.slice(0, 100) }, + 'Reviewer verdict detected from failed execution — stopping ping-pong', + ); + return; + } + } if (task.status !== 'active') { const now = new Date().toISOString(); updatePairedTask(taskId, { status: 'active', updated_at: now }); diff --git a/src/session-commands.test.ts b/src/session-commands.test.ts index 12948f3..8db23df 100644 --- a/src/session-commands.test.ts +++ b/src/session-commands.test.ts @@ -234,8 +234,6 @@ describe('handleSessionCommand', () => { ); }); - - it('sends denial to interactable sender in non-main group', async () => { const deps = makeDeps(); const result = await handleSessionCommand({