Impact
T3 silently discards most of the terminal scrollback it appears to configure. The deterministic reproduction retains only 800 of the intended 10,000 rows at 80 columns—a loss of more than 90%—and the effective count can be even lower at wider terminal sizes.
This means output from long-running agent commands, builds, test suites, logs, and stack traces can disappear from the terminal before users have a chance to inspect or copy it. There is no truncation marker or warning, so the terminal simply starts at a much later line when the user scrolls to the top. The loss happens inside the renderer and can occur even while the raw output remains below T3's separate 512 KiB client-buffer limit.
The affected renderer is used by web and desktop, and Android mirrors the same incorrect value in its native terminal bridge.
Before submitting
Area
apps/web (also affects desktop because it uses the web terminal, and Android mirrors the same configuration in its native bridge)
Problem statement
T3 configures the embedded Ghostty terminal with a value named MAX_SCROLLBACK_ROWS = 10_000 on web and kMaxScrollbackRows = 10000 on Android. The pinned libghostty-vt C header also describes max_scrollback as a maximum number of lines.
The pinned Ghostty implementation actually interprets that field as a byte budget. T3 is therefore requesting 10,000 bytes while its naming and the public header indicate an intended limit of 10,000 rows.
Ghostty does not retain a literal 10 KB buffer because it rounds/clamps the request to its internal page and viewport minimum. That masks the unit mismatch, but the resulting history is still far below 10,000 rows and varies with terminal width.
Steps to reproduce
A focused failing test using T3's actual vendored Ghostty WASM is available here:
Run it with:
pnpm --filter @t3tools/web exec vp test run --project unit src/terminal/ghostty/runtimeAbi.test.ts
The test creates an 80×10 terminal, writes 12,000 numbered lines, passes T3's current 10_000 value through the pinned C ABI, and reads the retained scrollback from Ghostty.
Expected behavior
The renderer should retain approximately the configured 10,000 scrollback rows, subject only to Ghostty's documented page-granularity approximation.
Actual behavior
Only 800 scrollback rows remain in the reproduction:
AssertionError: expected 800 to be greater than or equal to 10000
Older output is silently pruned by the renderer even when another T3 terminal-history layer still retains it. The exact number varies with terminal width because Ghostty budgets parsed grid cells rather than UTF-8 transcript bytes.
Cause
The Ghostty revision pinned by T3 has contradictory API documentation:
- Its public C header describes
max_scrollback as lines.
- Its implementation forwards the value to a byte-based screen/page budget.
- T3 follows the public-header interpretation and names the value as rows.
Upstream tracked this exact documentation mismatch in ghostty-org/ghostty discussion #12587. The latest upstream API resolves the ambiguity with separate byte and line settings in the ABI-breaking ghostty-org/ghostty PR #13481.
Version or commit
main at 1f8ed54add4133ac39effceded8fc1fff12d8e03, with vendored libghostty-vt revision 9f62873bf195e4d8a762d768a1405a5f2f7b1697.
Environment
Renderer-level and browser-independent. Reproduced directly against the vendored web WASM; the equivalent 10000 value is also present in the Android native terminal bridge.
Suggested fix
Prefer upgrading the canonical libghostty-vt pin to a revision containing the new explicit scrollback API, then migrate both embedders:
- Configure the intended 10,000-row behavior through
GHOSTTY_TERMINAL_OPT_SCROLLBACK_MAX_LINES.
- Configure
GHOSTTY_TERMINAL_OPT_SCROLLBACK_MAX_BYTES independently as a deliberate memory-safety budget. Do not inherit the low-level library's 10,000-byte default; when both limits are set, Ghostty applies whichever is reached first.
- Update the ABI regression test to use the new setters and prove that representative output retains approximately 10,000 rows.
- Keep the web/desktop and Android values aligned through the repository's canonical Ghostty integration rather than duplicating an ambiguous constant.
If upgrading Ghostty is too broad for the immediate fix, the smaller interim correction is to rename the current constants as bytes and use a deliberate byte budget. The Ghostty application version corresponding to T3's uncompressed pin used 10 MB, but this fallback would remain a byte budget and would not guarantee exactly 10,000 rows.
Testing decisions
- Keep the regression at the existing vendored-WASM ABI seam so it tests the artifact T3 ships rather than a mock.
- Add equivalent focused coverage for the Android C ABI configuration when migrating its native library.
- Verify representative narrow and wide terminal dimensions because a parsed-cell byte budget retains different row counts at different widths.
Workaround
There is no user-facing workaround. A source build can increase the current value, but its units must be treated as bytes rather than rows.
Out of scope
Making T3's separate raw client transcript and server terminal history limits configurable. Those limits operate on different representations and should be handled independently from this renderer configuration bug.
Impact
T3 silently discards most of the terminal scrollback it appears to configure. The deterministic reproduction retains only 800 of the intended 10,000 rows at 80 columns—a loss of more than 90%—and the effective count can be even lower at wider terminal sizes.
This means output from long-running agent commands, builds, test suites, logs, and stack traces can disappear from the terminal before users have a chance to inspect or copy it. There is no truncation marker or warning, so the terminal simply starts at a much later line when the user scrolls to the top. The loss happens inside the renderer and can occur even while the raw output remains below T3's separate 512 KiB client-buffer limit.
The affected renderer is used by web and desktop, and Android mirrors the same incorrect value in its native terminal bridge.
Before submitting
Area
apps/web(also affects desktop because it uses the web terminal, and Android mirrors the same configuration in its native bridge)Problem statement
T3 configures the embedded Ghostty terminal with a value named
MAX_SCROLLBACK_ROWS = 10_000on web andkMaxScrollbackRows = 10000on Android. The pinnedlibghostty-vtC header also describesmax_scrollbackas a maximum number of lines.The pinned Ghostty implementation actually interprets that field as a byte budget. T3 is therefore requesting 10,000 bytes while its naming and the public header indicate an intended limit of 10,000 rows.
Ghostty does not retain a literal 10 KB buffer because it rounds/clamps the request to its internal page and viewport minimum. That masks the unit mismatch, but the resulting history is still far below 10,000 rows and varies with terminal width.
Steps to reproduce
A focused failing test using T3's actual vendored Ghostty WASM is available here:
Run it with:
The test creates an 80×10 terminal, writes 12,000 numbered lines, passes T3's current
10_000value through the pinned C ABI, and reads the retained scrollback from Ghostty.Expected behavior
The renderer should retain approximately the configured 10,000 scrollback rows, subject only to Ghostty's documented page-granularity approximation.
Actual behavior
Only 800 scrollback rows remain in the reproduction:
Older output is silently pruned by the renderer even when another T3 terminal-history layer still retains it. The exact number varies with terminal width because Ghostty budgets parsed grid cells rather than UTF-8 transcript bytes.
Cause
The Ghostty revision pinned by T3 has contradictory API documentation:
max_scrollbackas lines.Upstream tracked this exact documentation mismatch in ghostty-org/ghostty discussion #12587. The latest upstream API resolves the ambiguity with separate byte and line settings in the ABI-breaking ghostty-org/ghostty PR #13481.
Version or commit
mainat1f8ed54add4133ac39effceded8fc1fff12d8e03, with vendoredlibghostty-vtrevision9f62873bf195e4d8a762d768a1405a5f2f7b1697.Environment
Renderer-level and browser-independent. Reproduced directly against the vendored web WASM; the equivalent
10000value is also present in the Android native terminal bridge.Suggested fix
Prefer upgrading the canonical
libghostty-vtpin to a revision containing the new explicit scrollback API, then migrate both embedders:GHOSTTY_TERMINAL_OPT_SCROLLBACK_MAX_LINES.GHOSTTY_TERMINAL_OPT_SCROLLBACK_MAX_BYTESindependently as a deliberate memory-safety budget. Do not inherit the low-level library's 10,000-byte default; when both limits are set, Ghostty applies whichever is reached first.If upgrading Ghostty is too broad for the immediate fix, the smaller interim correction is to rename the current constants as bytes and use a deliberate byte budget. The Ghostty application version corresponding to T3's uncompressed pin used 10 MB, but this fallback would remain a byte budget and would not guarantee exactly 10,000 rows.
Testing decisions
Workaround
There is no user-facing workaround. A source build can increase the current value, but its units must be treated as bytes rather than rows.
Out of scope
Making T3's separate raw client transcript and server terminal history limits configurable. Those limits operate on different representations and should be handled independently from this renderer configuration bug.