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);