Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/anchor-compaction-success-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nanocollective/nanocoder": patch
---

Fixed default compaction collapsing a multi-line tool result into `Result: success` whenever it mentioned "completed successfully" or "no errors" anywhere, which dropped failure lines that followed. Only a short single-line result is treated as a success marker now. Closes #1633.
54 changes: 54 additions & 0 deletions source/utils/message-compression.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,41 @@ test('compressMessages detects success in tool results', t => {
t.true(toolMsg?.content?.includes('success') ?? false);
});

test('compressMessages keeps a log that mentions success but also reports failures', t => {
const tokenizer = createMockTokenizer();
const messages: Message[] = [
createToolMessage(
'execute_bash',
'Build completed successfully\nfailures: 3\nsee report.txt',
),
createUserMessage('Recent'),
];

const result = compressMessages(messages, tokenizer, {
mode: 'default',
keepRecentMessages: 1,
});

const toolMsg = result.compressedMessages[0];
t.false(toolMsg?.content?.includes('Result: success') ?? true);
});

test('compressMessages treats a single "completed successfully" line as success', t => {
const tokenizer = createMockTokenizer();
const messages: Message[] = [
createToolMessage('execute_bash', 'Build completed successfully'),
createUserMessage('Recent'),
];

const result = compressMessages(messages, tokenizer, {
mode: 'default',
keepRecentMessages: 1,
});

const toolMsg = result.compressedMessages[0];
t.true(toolMsg?.content?.includes('Result: success') ?? false);
});

// ==================== keepRecentMessages option ====================

test('compressMessages respects custom keepRecentMessages value', t => {
Expand Down Expand Up @@ -603,3 +638,22 @@ test('compressMessages preserves display-only messages in output but excludes th
);
t.is(result.compressedTokenCount, expectedCompressedTokens);
});

test('compressMessages keeps the trailing detail of a one-line success', t => {
const tokenizer = createMockTokenizer();
const messages: Message[] = [
createToolMessage('execute_bash', 'Operation completed successfully in 5s'),
createUserMessage('Recent'),
];

const result = compressMessages(messages, tokenizer, {
mode: 'default',
keepRecentMessages: 1,
});

const toolMsg = result.compressedMessages[0];
t.is(
toolMsg?.content,
'Tool: execute_bash\nResult: Operation completed successfully in 5s',
);
});
15 changes: 11 additions & 4 deletions source/utils/message-compression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,16 +368,23 @@ function extractErrorInfo(content: string): {
return null;
}

// Check if content indicates success
// Check if content indicates success.
// Only a short, single-line result counts: a multi-line log that happens to
// contain "completed successfully" may also report failures further down,
// so it must not be collapsed into "Result: success" (#1633).
function isSuccess(content: string): boolean {
const text = content.trim();
if (text.includes('\n')) {
return false;
}
const successPatterns = [
/^success$/i,
/^ok$/i,
/^done$/i,
/completed successfully/i,
/no errors/i,
/^.{0,80}completed successfully\.?$/i,
/^no errors\.?$/i,
];
return successPatterns.some(pattern => pattern.test(content));
return successPatterns.some(pattern => pattern.test(text));
}

// Check for error in content
Expand Down
Loading