#10612: fix: trim leading blank lines on first emitted chunk only (#5530)
agents
stale
## Problem
Anthropic models emit a `"\n\n"` text block before thinking blocks. The existing `.trimEnd()` in `emitBlockChunk` only strips trailing whitespace, so leading newlines pass through to iMessage output as blank lines before the agent's response.
## Root Cause
1. **Streaming path (`emitBlockChunk`):** `stripBlockTags(text, state.blockState).trimEnd()` preserves leading `\n\n`.
2. **Single-block path (`sanitizeUserFacingText`):** `collapseConsecutiveDuplicateBlocks()` returns `text` (untrimmed) when `blocks.length < 2`.
## Fix
**Targeted first-chunk-only trim** instead of the broader `.trim()` on every chunk (which risks stripping meaningful indentation at chunk boundaries):
### `emitBlockChunk` (streaming path)
```ts
const raw = stripBlockTags(text, state.blockState);
const cleaned =
state.lastBlockReplyText === undefined
? raw.replace(/^(?:[ \t]*\n)+/, "")
: raw;
const chunk = cleaned.trimEnd();
```
Only the **first** emitted chunk (when `lastBlockReplyText` is still `undefined`) gets leading blank lines stripped. Subsequent chunks use `.trimEnd()` as before, preserving indentation in code blocks.
### `sanitizeUserFacingText` (final text path)
```ts
const cleaned = stripped.replace(/^(?:[ \t]*\n)+/, "");
```
Strips leading blank lines after removing `<final>` tags but before error detection and collapse logic.
## Why `/^(?:[ \t]*\n)+/` instead of `.trim()`
- Removes only **leading blank lines** (lines containing only spaces/tabs followed by newline)
- **Preserves indentation** on the first content line (e.g. `\n\n indented code` → ` indented code`)
- Only applied on the **first chunk**, not every streaming chunk
## Testing
- Added test case for `sanitizeUserFacingText` verifying leading blank line stripping
- All 8 existing tests pass
- No type errors
Supersedes #9285. Closes #5530.
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
- Trims leading blank lines only on the first emitted streaming chunk (to avoid Anthropic pre-thinking `\n\n` showing up as empty lines), while keeping `.trimEnd()` behavior for subsequent chunks.
- Extends `sanitizeUserFacingText()` to strip leading blank lines after removing `<final>` tags and adds a unit test covering this behavior.
- Adds an auto-compaction retry safeguard that can downgrade the system prompt (drop injected workspace files) when Pi’s internal retry would still overflow.
- Updates compaction lifecycle events to propagate a user-facing “retry canceled” message when compaction succeeds but retry is blocked by prompt size.
<h3>Confidence Score: 3/5</h3>
- This PR is close to mergeable, but dependency range changes and internal session-field restoration behavior need attention.
- Core trimming/sanitization changes look targeted and covered by tests, but switching pi-* deps to caret ranges reduces reproducibility and the new retry hook logic mutates/restores private session fields in a way that can subtly change session behavior.
- package.json, src/agents/pi-embedded-runner/run/attempt.ts
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#12325: fix: trim leading/trailing whitespace from outbound messages
by jordanstern · 2026-02-09
83.5%
#14946: fix(webchat): accumulate text across blocks in streaming buffer
by mcaxtr · 2026-02-12
79.0%
#12064: fix: prevent chunker from truncating messages that fit within limit
by joetomasone · 2026-02-08
78.3%
#16733: fix(ui): avoid injected newlines when tool output is hidden
by jp117 · 2026-02-15
78.2%
#20795: fix(markdown): prevent triple newlines after blockquotes
by novalis133 · 2026-02-19
77.7%
#12180: fix: merge multi-block assistant texts into single reply
by 1960697431 · 2026-02-08
77.7%
#21174: fix(bluebubbles): trim leading newlines from message text
by cosmopax · 2026-02-19
77.3%
#23462: fix: extract thinking blocks as fallback in extractTextFromChatContent
by nszhsl · 2026-02-22
75.4%
#23271: fix(chat): strip untrusted metadata blocks from Control UI messages
by lbo728 · 2026-02-22
75.4%
#17304: feat(gemini): robust handling for non-XML reasoning headers (`Think...
by YoshiaKefasu · 2026-02-15
75.3%