Skip to content
Source
Sign inSign up

[Bug]: iOS terminal font and theme changes resend old query replies to the PTY #36000

Send to my agent
Opencoygeek(GitHub profile, opens in a new tab) opened this issue 0 comments

Summary

Changing the native iOS terminal's font or theme can send unsolicited terminal replies to the running shell or program. The view retains live output, including device queries, and replays it into a replacement surface with its write callback active. Source analysis of the adapter and its pinned Ghostty fork establishes this path; no iOS runtime reproduction was run. Appearance changes should restore the display without generating fresh PTY input from historical output.

Before submitting

  • Related issues: searched open and closed issues and PRs; related history is listed under Additional context.
  • Removed credentials, tokens, private paths, and sensitive data.

Area

apps/mobile

Actual behavior

Source-only finding: after live output contains the operating-status query ESC [ 5 n, changing fontSize, appearanceScheme, or themeConfig calls resetSurface(). The next layout creates a new surface, registers its write callback, and feeds streamedOutput.data into it. The pinned fork parses the old query again and queues ESC [ 0 n; the active callback emits those bytes through onInput, which the writable, running mobile terminal routes to terminal.write. No new query from the remote program is needed for this second reply.

Steps to reproduce

  1. Source-traced scenario, not executed on a simulator or device: open a writable native iOS terminal with streaming output support (outputRevision 1), attach to a running session, and allow its initial snapshot to finish.
  2. Have a disposable raw-mode byte observer on that session output the bytes 1b 5b 35 6e once and record incoming bytes. The live reply is 1b 5b 30 6e.
  3. Without sending another query, use the terminal's font-size control to change its font. Keep total output below the native 512 KiB retention cap.
  4. The source path sends the same reply again while replaying the retained live query into the new surface. A theme change reaches the same path.

Runtime reproduction was not run. The evidence is the complete source path below, including the fork's FIFO callback registration and feed processing. A simulator/device build, a connected disposable session, and native event or PTY-byte capture would supply the runtime observation and frequency.

Reproduction script: NOT_ENOUGH_INFO - no iOS app or simulator session was run; an executable script needs a native client, an isolated running terminal session, and automation for the font-size control with PTY-byte capture.

Expected behavior

A font or theme change restores the native terminal's display without replying to historical queries. The product explicitly requires that restoring scrollback must not send terminal replies to the current shell in docs/internals/terminal-runtime.md#L40-L44. Live queries still receive their normal reply once.

Impact

Minor bug or occasional failure.

Impact: P2 - an ordinary appearance change sends unsolicited input to a running terminal session; the fault is bounded to replayed queries retained by that mounted iOS view.

The source establishes extra PTY input, not a particular shell's visible echo or command execution. Shell/TUI handling, observed device frequency, and released-app version range are NOT_ENOUGH_INFO - a native runtime capture and release mapping would establish these. Keeping font and theme unchanged avoids this recreation trigger. Read-only or stopped views do not forward native input through the JS handler. Server-history sanitization does not protect this case because the native buffer retains live output before recreation.

Version or commit

T3 Code source commit 59e64eef881632a23f89c4138bcf37f23d92bf93; iOS Ghostty fork revision cf8edc23f3a6a87a96e41a90013e89e987d34980, as recorded in the native module README. No installed or released iOS binary was tested.

Environment

iOS native Expo terminal with use_custom_io = true and streaming output capability. No UIKit runtime, simulator, or physical device was exercised; exact affected iOS versions are NOT_ENOUGH_INFO - a runtime check is needed.

Interface

UI: native mobile terminal font-size and appearance controls; native onInput event and terminal write RPC.

Earliest failure stage

Cleanup or lifecycle: surface recreation reprocesses retained live queries with the write callback installed.

Evidence

  • Expected source: the terminal-runtime contract above explicitly forbids PTY replies during scrollback restoration.
  • Failure source: the adapter retains live output, installs its callback before replay, and forwards callback bytes as input; the pinned fork answers operating-status requests.
  • Evidence provenance: static.
  • Local verification: static-proof; runtime reproduction was not run.
  • Reproduction completeness: incomplete.
  • Missing fact: a simulator/device capture of the second reply after an actual font or theme change, plus affected released-build mapping.
  1. The mobile terminal exposes font-size controls and passes their preference to the native surface: apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx#L129-L145, apps/mobile/src/features/terminal/NativeTerminalSurface.tsx#L276-L295.
  2. The three appearance setters call resetSurface(): apps/mobile/modules/t3-terminal/ios/T3TerminalView.swift#L284-L289, apps/mobile/modules/t3-terminal/ios/T3TerminalView.swift#L320-L332.
  3. Live output is retained before being fed, and replay history is a raw bounded byte tail: apps/mobile/modules/t3-terminal/ios/T3TerminalView.swift#L769-L783, apps/mobile/modules/t3-terminal/ios/TerminalOutputBuffer.swift#L3-L28.
  4. Surface creation enables custom I/O, installs the write callback, then feeds retained data. Reset schedules layout, which creates the replacement surface: apps/mobile/modules/t3-terminal/ios/T3TerminalView.swift#L422-L437, apps/mobile/modules/t3-terminal/ios/T3TerminalView.swift#L675-L706.
  5. The fork enqueues callback registration and feed messages on the same I/O mailbox and processes them in order: src/apprt/embedded.zig#L2518-L2558, src/termio/Thread.zig#L368-L385. Feed processes terminal output through its stream parser: src/termio/Termio.zig#L663-L711.
  6. The fork answers operating-status requests with ESC [ 0 n, and its callback backend forwards queued writes: src/termio/stream_handler.zig#L815-L820, src/termio/Thread.zig#L350-L359, src/termio/Callback.zig#L87-L117.
  7. T3 forwards valid UTF-8 callback bytes to onInput, and the running writable view sends them through the terminal write command: apps/mobile/modules/t3-terminal/ios/T3TerminalView.swift#L802-L816, apps/mobile/src/features/terminal/NativeTerminalSurface.tsx#L255-L260, apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx#L602-L619.
  8. The server sanitizes retained history but publishes the original live output, so stripping queries on the server does not sanitize the native view's live replay buffer: apps/server/src/terminal/Manager.ts#L2321-L2342.

Restoration check

With one live operating-status query retained, record exactly one live reply; change font and theme and require zero additional query replies. Then issue a fresh query and require its one reply, to ensure the restoration check cannot pass by disabling live responses.

References

  • https://github.com/pingdotgg/t3code/issues/35511 — concerns DA3 surviving server-persisted history and Android reopen; this report concerns ordinary status queries in an already-mounted iOS view's retained live output during appearance changes, even with server sanitization working.
  • https://github.com/pingdotgg/t3code/pull/24261 — introduces bounded native replay storage and notes imperfect raw-ANSI recreation; it does not describe query replies being forwarded on appearance changes.
  1. tanstack-source added labelsbug

  2. github-actions[bot](GitHub profile, opens in a new tab) added labelsvouch:denounced

Sign in to join this conversation.