Skip to content

fix(coding-agent): seven session and IO correctness defects - #2037

Open
snimu wants to merge 7 commits into
mainfrom
fix/session-io-defects
Open

fix(coding-agent): seven session and IO correctness defects#2037
snimu wants to merge 7 commits into
mainfrom
fix/session-io-defects

Conversation

@snimu

@snimu snimu commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Seven small, unrelated session/IO correctness defects, each with a regression pin:

  • Compaction cannot silently keep everything. When the keep-recent budget is crossed inside a run of trailing tool results (past every valid cut point), findCutPoint fell back to the first cut point — keeping essentially the whole history and making the compaction a no-op exactly when the session most needed shrinking. The fallback now keeps only the final turn. Fixes the defect reported in discussion findCutPoint silently keeps the whole window when a turn's tool results exceed keepRecentTokens, so compaction becomes a no-op #1555.
  • A retry can no longer wedge the session. The auto-retry's scheduled agent.continue() swallowed every rejection; a continue that never starts (busy, nothing-to-continue) left isRetrying stuck true and every retry waiter parked forever. The rejection now ends the retry (auto_retry_end, state reset, waiters released). Fixes the defect reported in discussion [Bug] fix(coding-agent): auto-retry leaves isRetrying stuck when agent.continue() fails #1812.
  • Saved subagents with a lost parent edge display as subagents. The archived-session classifier keyed only on parentSessionPath; a session with rlmDepth > 0 whose parent edge was reconciliation-dropped was presented as top-level. Depth now decides, with the path as legacy fallback. Fixes the defect reported in discussion [Bug] Top-level forked sessions are nested as subagents in Agents View #1635.
  • Tail truncation rescues oversized lines again. truncateTail splits on \n, so newline-terminated output contributes a trailing empty line that defeated the oversized-line rescue (outputLinesArr.length === 0) — returning empty output instead of the line's tail. The rescue now fires when only empty lines have been collected. Fixes the defect reported in discussion truncateTail returns empty content when the last line exceeds the byte budget, so long single-line output reaches the model as (no output) #1551.
  • The output spill has one owner and a coherent terminal contract. The temp-file spill existed twice (OutputAccumulator and an inline copy in the bash executor), both created write streams with no error listener — an ENOSPC/unwritable tmpdir emitted an unhandled error event, killing the process mid-command — and neither had a terminal contract: a failed final flush could reject closeTempFile() (turning a degraded spill into a tool failure), the executor returned before its flush settled (advertising a partial file), and the formatter printed Full output: undefined after a cleared path. Both paths now share one OutputSpill with exactly two terminal states: a complete advertised file (results are built only after finalize() settles) or a degraded spill — any failure at open, write, or final flush clears the path everywhere and the tool call still succeeds with the truncated in-memory tail. The redundant persistIfTruncated snapshot option is deleted (append/finish already open the spill on a strict superset of its condition). Fixes the defect reported in discussion Temp-file open failure kills the process: createWriteStream has no error listener in either output path #1552 and the three review findings on its first round.
  • Piped stdin no longer glues onto the instruction. echo data | prime-agent -p "summarize" joined the parts with an empty string, welding the last stdin line to the first instruction word. Parts join with a blank line. Fixes the defect reported in discussion [Bug] Piped stdin and the CLI instruction are concatenated without a separator #1396.
  • Frontmatter parses behind a UTF-8 BOM. A BOM before --- made the shared parser (skills, prompts, themes) silently treat the whole file as body. The parser's normalization strips a leading BOM, fixing all call sites at once. Fixes the defect reported in discussion [Bug] UTF-8 BOM prevents SKILL.md frontmatter from loading #1397.

Validation

  • seven pins, one per defect, each verified fail-unfixed against the pre-fix code (the retry pin hangs pre-fix — the exact user-visible symptom)
  • suites: compaction (29), agent-session-retry-events (29), agents-view-state (75), initial-message (3), frontmatter (9), bash-tool, suite/agent-session-compaction, plus new truncate and output-accumulator pins — 185 tests, 0 failures, sanitized env
  • root npm run check passes via the pre-commit hook

LOC

Total src: +142/−90 (net +52); tests: +258/−7 (net +251).
Src +59/−17 (round 1, point fixes) + 94/−101 (round 2 spill consolidation — net src −7: one lifecycle instead of two copies plus a deleted redundant option). Tests +122/−3 and +62/−1 (three acceptance pins for the terminal contract, each verified fail-unfixed: never-rejecting close, complete-file-only advertisement, no Full output: undefined). Mechanism split: round 2 is consolidation — OutputSpill replaces two divergent inline lifecycles.

Linear: RES-1266 https://linear.app/primeintellect/issue/RES-1266


Note

Medium Risk
Touches compaction cut logic, retry lifecycle, and bash output paths used during tool execution; changes are localized with regression tests but affect long-session behavior and error recovery.

Overview
Bundles seven unrelated correctness fixes across compaction, retries, agents UI, bash/output handling, CLI prompts, and frontmatter parsing.

Compaction now defaults to the final turn when the keep-recent token budget is exceeded inside trailing tool results (no cut point at/after that index), instead of falling back to the first cut point and effectively keeping the whole history.

Auto-retry bumps a _retryGeneration on resolve and, if the deferred agent.continue() rejects while still the active retry, emits a failed auto_retry_end, clears retry state, and releases waiters—stale continue callbacks from aborted retries are ignored.

Bash / tool output consolidates temp-file spilling into shared OutputSpill: stream errors are handled (no process crash), finalize() never rejects, full-output paths are advertised only after the file is complete, and degraded spills omit the path and Full output: undefined messaging. truncateTail again rescues oversized final lines when output ends with newline-induced blank lines.

CLI joins piped stdin, file text, and the first message with \n\n instead of empty-string concatenation. Frontmatter strips a leading UTF-8 BOM before --- matching. Agents view treats archived saved sessions with rlmDepth > 0 as subagents even when parentSessionPath is missing.

Reviewed by Cursor Bugbot for commit 41b5d72. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix seven session and IO correctness defects in coding-agent

  • AgentSession retry lifecycle: rejected scheduled continuations now terminate the retry, emit a failed auto-retry-end event, and clear retry state. Stale continuations are ignored.
  • Introduces OutputSpill manager for bash command output. Temp file failures degrade to an undefined path without rejecting finalization. The bash tool only advertises a full-output path after the file settles.
  • compaction.findCutPoint falls back to the final valid cut point when the budget is crossed in trailing tool results, instead of retaining the entire history.
  • truncateTail preserves the end of an oversized final line with trailing blank lines within the byte budget.
  • agents-view.summaryForUnifiedRecord uses positive rlmDepth for subagent classification. utils.normalizeNewlines strips a leading UTF-8 BOM. cli.buildInitialMessage separates prompt parts with blank lines.
  • Risk: OutputSpill replaces direct temp stream management in core.executeBashWithOperations and tools.createBashToolDefinition; check spill finalization paths to ensure temp files are cleaned up and bash output paths are not lost.

Macroscope summarized 41b5d72.

Comment thread packages/coding-agent/src/core/bash-executor.ts Outdated
Comment thread packages/coding-agent/src/core/tools/output-accumulator.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ce12e55. Configure here.

Comment thread packages/coding-agent/src/core/tools/output-accumulator.ts Outdated
The bash executor's inline spill copy is folded into a shared OutputSpill with exactly two terminal states: a COMPLETE advertised file (finalize resolves its path after the flush settles) or a DEGRADED spill (failure at open, write, or final flush) whose path is cleared everywhere and whose tool call still succeeds with the in-memory tail. closeTempFile never rejects for spill reasons; results are built only after finalize; the bash formatter never prints 'Full output: undefined'. snapshot's persistIfTruncated option is deleted - append/finish already open the spill on a strict superset of its condition.
…truthful rescue metadata

A degraded spill best-effort unlinks its partial file after the stream closes (the leak squatted on the very disk pressure that caused the degrade). The retry-continue rejection path marks captured provider auth failures stale before clearing them, like every other terminal retry path. The tail rescue keeps trailing blank lines in the output (their newlines charged to the byte budget) so line metadata describes the real file, and the bash label names the partial line by its actual number, omitting a zero-byte line size.
Comment thread packages/coding-agent/src/core/tools/truncate.ts
Trailing blank lines kept by the rescue now fit WITHIN maxBytes (at most maxBytes-1 of them, their join newlines charged to the budget; excess blanks are dropped), so truncateTail's byte cap holds for every input. Property pin sweeps a blank-shape corpus across small budgets.
Comment thread packages/coding-agent/src/core/agent-session.ts Outdated
Comment thread packages/coding-agent/src/core/tools/output-accumulator.ts
…ry callbacks

The degraded-spill unlink runs in a 'close' event handler where an EACCES/EBUSY/ENOTDIR would kill the process (force only suppresses ENOENT); cleanup is now genuinely best-effort. A retry-generation token (bumped by every retry resolution) fences the scheduled continue's rejection callback, so an aborted retry's parked continue can no longer clear a newer retry's state or emit its end event.
Comment thread packages/coding-agent/src/core/tools/output-accumulator.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant