Problem
The scrollback buffer size is user-configurable with a default of `5000` lines:
```ts
// pane.ts:148
scrollback: config.scrollback,
```
Each xterm.js line with 200 columns uses ~1.6KB of memory (cell data + attribute data). With the default 5000 lines:
- Per pane: ~8MB of scrollback buffer memory
- 10 panes: ~80MB just for terminal scrollback
But the real problem is that xterm.js allocates memory proportional to the widest line ever rendered, not the current column count. A single wide line (e.g., 1000-character error message) can permanently inflate the per-line memory for all subsequent lines in that buffer.
With agents producing heavy output
AI agents like Claude Code can produce thousands of lines of output per session. Users who increase scrollback to 10,000+ lines (common for agent workflows) can see:
- 10 panes × 10,000 lines × 1.6KB = 160MB
- Plus hidden-tab pending write buffers (512KB cap per pane = 5MB for 10 panes)
Code reference
`config-types.ts:128` — `scrollback: number` in config
`pane.ts:82` — `MAX_HIDDEN_PENDING_BYTES = 512 * 1024` (512KB per pane)
Proposed solution
1. Adaptive scrollback based on tab count
Reduce scrollback dynamically when many tabs are open:
```ts
const effectiveScrollback = Math.max(
1000,
Math.floor(config.scrollback / Math.max(1, totalPaneCount / 4))
);
```
2. Virtual scrollback with disk backing
For long-running agent sessions, page older scrollback to disk instead of keeping it in memory:
- Keep the most recent 2000 lines in xterm.js
- Write older lines to a temporary file on disk
- Load from disk when the user scrolls back
3. Trim scrollback on hidden tabs
When a tab is hidden, trim its scrollback to a minimum (e.g., 1000 lines). Restore when shown:
```ts
hide() {
// Compact scrollback for memory savings
this.terminal.options.scrollback = 1000;
this.terminal.clear(); // Frees excess buffer memory
}
```
4. Reduce hidden-tab pending buffer
The current 512KB cap per hidden pane is generous. For 10 hidden panes, that's 5MB of raw PTY data queued in JS arrays. Consider reducing to 128KB.
5. Memory usage monitoring
Add a diagnostic command that shows per-tab memory usage:
```ts
// Command palette: "Show Memory Usage"
for (const [id, tab] of this.tabs) {
const lines = tab.terminal.buffer.active.length;
const estMb = (lines * 1600 / 1_000_000).toFixed(1);
console.log(`Tab ${id}: ${lines} lines (~${estMb} MB)`);
}
```
Impact
- Memory: 80-160MB+ consumed by scrollback buffers alone with 10+ panes
- GC pressure: Large xterm.js buffers increase V8 GC pause times
- Swap thrashing: On 8GB machines, heavy multi-tab usage can push into swap
- OOM risk: Users with 20+ tabs and high scrollback can exhaust available memory
Metrics to validate
- Measure actual process memory (`process.memoryUsage()`) vs tab count
- Profile xterm.js buffer memory per scrollback setting
- Test memory usage with 20 tabs, 10k scrollback, agents producing output
Problem
The scrollback buffer size is user-configurable with a default of `5000` lines:
```ts
// pane.ts:148
scrollback: config.scrollback,
```
Each xterm.js line with 200 columns uses ~1.6KB of memory (cell data + attribute data). With the default 5000 lines:
But the real problem is that xterm.js allocates memory proportional to the widest line ever rendered, not the current column count. A single wide line (e.g., 1000-character error message) can permanently inflate the per-line memory for all subsequent lines in that buffer.
With agents producing heavy output
AI agents like Claude Code can produce thousands of lines of output per session. Users who increase scrollback to 10,000+ lines (common for agent workflows) can see:
Code reference
`config-types.ts:128` — `scrollback: number` in config
`pane.ts:82` — `MAX_HIDDEN_PENDING_BYTES = 512 * 1024` (512KB per pane)
Proposed solution
1. Adaptive scrollback based on tab count
Reduce scrollback dynamically when many tabs are open:
```ts
const effectiveScrollback = Math.max(
1000,
Math.floor(config.scrollback / Math.max(1, totalPaneCount / 4))
);
```
2. Virtual scrollback with disk backing
For long-running agent sessions, page older scrollback to disk instead of keeping it in memory:
3. Trim scrollback on hidden tabs
When a tab is hidden, trim its scrollback to a minimum (e.g., 1000 lines). Restore when shown:
```ts
hide() {
// Compact scrollback for memory savings
this.terminal.options.scrollback = 1000;
this.terminal.clear(); // Frees excess buffer memory
}
```
4. Reduce hidden-tab pending buffer
The current 512KB cap per hidden pane is generous. For 10 hidden panes, that's 5MB of raw PTY data queued in JS arrays. Consider reducing to 128KB.
5. Memory usage monitoring
Add a diagnostic command that shows per-tab memory usage:
```ts
// Command palette: "Show Memory Usage"
for (const [id, tab] of this.tabs) {
const lines = tab.terminal.buffer.active.length;
const estMb = (lines * 1600 / 1_000_000).toFixed(1);
console.log(`Tab ${id}: ${lines} lines (~${estMb} MB)`);
}
```
Impact
Metrics to validate