Skip to content

fix(tui): trim shell-only transcript turns and cap stored output - #2621

Open
he-yufeng wants to merge 1 commit into
MoonshotAI:mainfrom
he-yufeng:fix/shell-transcript-tail-turns
Open

fix(tui): trim shell-only transcript turns and cap stored output#2621
he-yufeng wants to merge 1 commit into
MoonshotAI:mainfrom
he-yufeng:fix/shell-transcript-tail-turns

Conversation

@he-yufeng

Copy link
Copy Markdown
Contributor

Related Issue

Resolve #2556

The linked issue covers two leaks. The folded-entry half is already in flight as #2603; this PR takes the other half, the ! shell path.

Problem

A session that leans on ! grows memory without bound, for two reasons that compound:

  • Both the echo and the output entry go in with turnId: undefined, so groupTurns folds the whole stretch into one tail turn, and turnsToTrim never touches the last turn. Entry count climbs forever.
  • finishShellOutput stores the complete stdout/stderr twice: once inside ShellRunComponent (finalStdout/finalStderr) and once formatted into the transcript entry. The 256 KB running cap only applies mid-stream, so a single ! cat bigfile pins its full size for the rest of the session.

What changed

  • groupTurns now flushes the buffered undefined run when the next user entry arrives. A real prompt still lands in the turn that follows it (that case has no buffered run yet), while each ! echo starts a fresh group, so old commands trim away under the normal window together with their components. The existing removal path already handles this shape: the echo is a boundary component, so entries and components stay in lockstep.
  • finishShellOutput caps each stored stream at the last 64 KB with a one-line … (N earlier chars truncated) marker, matching the scale of the running buffer's keep. The tail is what matters after a command spews.

Relationship to #2385: that PR bounds the rendered frame and deliberately keeps full output in the entry for export; this one bounds retention, which is the part that actually leaks. They compose, and this diff does not touch the render path it changes.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

Verification

  • New unit tests: ! stretch splits into one turn per command and old commands trim, prompt flow unchanged, cap keeps the tail with an accurate dropped count and never starts it on a lone low surrogate.
  • test/tui suite: 1786 passed. tsc --noEmit clean for the touched files, oxlint clean.

A !-heavy stretch grouped every echo and output into one tail turn that
turnsToTrim can never touch, and each finished command kept its full
stdout/stderr in both the component and the transcript entry. Split the
undefined run at each shell echo so old commands trim with the normal
window, and cap each stored stream at the last 64 KB with a truncation
marker.
@changeset-bot

changeset-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 43446ed

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Aug 4, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@43446ed
npx https://pkg.pr.new/@moonshot-ai/kimi-code@43446ed

commit: 43446ed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 43446ed556

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +90 to +92
if (entry.kind === 'user' && pendingUndefined.length > 0) {
turns.push({ turnId: undefined, entries: pendingUndefined });
pendingUndefined = [];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid splitting boundaryless entries into trimmable turns

When an undefined-turn, non-boundary entry is pending (for example the goal-completion assistant card appended without a turnId), this flush runs as soon as the next real user prompt is appended and creates a trimmable turn with no user/skill/plugin boundary. trimTranscriptWindow removes mounted components by counting those boundaries, so if such a boundaryless turn is selected for trimming (e.g. with aggressive KIMI_CODE_TUI_HYSTERESIS=0, or a run of several such entries under the default hysteresis), the entry is dropped from transcriptEntries but its component remains mounted, leaving stale UI and unreclaimed memory. Restrict the split to actual shell-command runs or teach the trim path to remove boundaryless turns too.

Useful? React with 👍 / 👎.

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.

TUI memory leak: ! shell output entries grow unbounded; folded entries are never reclaimed

1 participant