From 15b6d2075ec09e4d8feba1a456ae4206659e941d Mon Sep 17 00:00:00 2001 From: RAJVEER42 Date: Fri, 22 May 2026 02:43:52 +0530 Subject: [PATCH] fix: claude prompt mode signals error on max_tokens truncation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scanBatchWithPrompt used max_tokens: 1024 but never checked stop_reason on the response. When the API hit the token limit, extractFindings found no tool_use block and returned { findings: [] } — identical to a clean empty result. No error was logged, errorCount stayed at zero, and findings were silently dropped. Added stop_reason check before extractFindings: a max_tokens response now returns { error: true } and logs an error, so the batch is counted as failed and the caller knows findings may be incomplete. --- .changeset/lemon-impalas-yawn.md | 5 +++++ src/__tests__/adapters/claude.test.ts | 21 +++++++++++++++++++++ src/adapters/claude.ts | 5 +++++ 3 files changed, 31 insertions(+) create mode 100644 .changeset/lemon-impalas-yawn.md diff --git a/.changeset/lemon-impalas-yawn.md b/.changeset/lemon-impalas-yawn.md new file mode 100644 index 0000000..b32b213 --- /dev/null +++ b/.changeset/lemon-impalas-yawn.md @@ -0,0 +1,5 @@ +--- +"layne": patch +--- + +fix: claude prompt mode now signals an error when the API response is truncated by max_tokens instead of silently returning empty findings diff --git a/src/__tests__/adapters/claude.test.ts b/src/__tests__/adapters/claude.test.ts index 9eb5562..f95f85a 100644 --- a/src/__tests__/adapters/claude.test.ts +++ b/src/__tests__/adapters/claude.test.ts @@ -221,6 +221,27 @@ describe('runClaude()', () => { expect(findings).toEqual([]); }); + it('logs an error when stop_reason is max_tokens instead of silently dropping findings', async () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + mockReadFile.mockResolvedValueOnce(DUMMY_CONTENT); + mockCreate.mockResolvedValueOnce({ + stop_reason: 'max_tokens', + content: [], + }); + + await runClaude({ + workspacePath: WORKSPACE, + changedFiles: CHANGED_FILES, + toolConfig: ENABLED_CONFIG, + }); + + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('max_tokens'), + ); + consoleSpy.mockRestore(); +}); + + it('passes the configured model to the API', async () => { mockReadFile.mockResolvedValueOnce(DUMMY_CONTENT); mockCreate.mockResolvedValueOnce(cleanResponse()); diff --git a/src/adapters/claude.ts b/src/adapters/claude.ts index 20a0203..5f6bd27 100644 --- a/src/adapters/claude.ts +++ b/src/adapters/claude.ts @@ -218,6 +218,11 @@ async function scanBatchWithPrompt( tool_choice: { type: 'any' }, }); + if (response.stop_reason === 'max_tokens') { + console.error('[claude] API error during scan batch (prompt mode): response truncated — max_tokens reached, findings may be incomplete'); + return { error: true }; + } + return extractFindings(response.content); } catch (err) { console.error('[claude] API error during scan batch (prompt mode):', (err as Error).message ?? err);