[Bug]: Ghostty scrollback is configured as bytes despite being named as rows
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
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
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
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_scrollbackas 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_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. - 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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The regression seam is apps/web/src/terminal/ghostty/runtimeAbi.test.ts; run the focused pnpm --filter @t3tools/web exec vp test run --project unit src/terminal/ghostty/runtimeAbi.test.ts command first and inspect the current web Ghostty configuration plus the Android native bridge value. Update both embedders using the explicit scrollback API described in the issue, then verify approximately 10,000 rows across narrow and wide terminals and add equivalent Android ABI coverage.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, c, typescript, wasm
- Domain
- desktop-dev, frontend, mobile-dev
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100