repo-mailbox/docs/ghostty-orchestration-measurement.md
Kjell Tore Guttormsen 5d9c76763d docs(board): measure what Ghostty 1.3.1 can actually be driven to do
The gate question before any day-plan design: can Ghostty be scripted to open
a tab with two vertical panes, each in its own directory, with a command
running in one? Measured against the installed 1.3.1 rather than reasoned
about, because this is undocumented platform behavior.

Yes, but not through the documented API. new window / new tab / split return
correctly shaped objects that never spawn a shell - nine probe windows, shell
count flat at 29 - and every call that needs the process fails with "Terminal
surface model is not available". That is issue #12730, a 1.3.1 regression
closed as not planned, and 1.3.1 is the newest release. surface configuration
is collateral: all four of its useful properties ride that path.

What does work is perform action on an ALREADY REALIZED surface. new_tab and
new_split produce live shells (29 -> 31), and input text + send key drives
them - two panes cd'd to different repos reported distinct directories and
distinct PIDs. The working directory becomes a typed cd, so the broken path is
not needed at all.

Three constraints fell out of the measurement and are recorded because each
was found by breaking something: pane targeting must diff tab ids, since
last tab of w is not the new tab and a probe that trusted position typed into
a tab it did not create; new tabs inherit the anchor's directory; and
count of terminals is not a pane count.

Recommends splitting the work - the plan here as a third rendering of the
board scan, the Ghostty driver in a separate repo. The argument is the
measurement: a preview API that breaks in 1.4, whose documented path is
already broken, with a blast radius reaching into other repos' live sessions,
does not belong behind the mailbox.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GfDGWyyhnM26J4p93GSk2L
2026-08-02 06:15:47 +02:00

7.4 KiB

Driving a day plan into Ghostty: what is actually possible on 1.3.1

Measured 2026-08-01/02 against the installed terminal, not reasoned about. This document exists to answer one gate question before any design: can Ghostty be scripted to open a tab with two vertical panes, each in its own directory, with a command running in one?

Short answer: yes, but not through the documented API - the documented path is broken in the installed version, and the path that works is an undocumented composition of primitives that the maintainer has labelled preview.

The environment being measured

  • Ghostty 1.3.1 (stable, released 2026-03-13). This is the newest release; there is no 1.3.2 and no 1.4.
  • Config: working-directory = inherit, window-save-state = always, shell-integration = zsh, macos-titlebar-style = tabs.
  • Operator's workspace: one window, 13 tabs, 2 vertical panes per tab.

What was measured

Mechanism Realizes a surface? Splits? Can start a command?
AppleScript new window / new tab / split No structure only, panes blank no
ghostty +new-window not available on macOS - -
open -na Ghostty.app --args ... yes (26 shells) yes, whole layout no, and it duplicates everything
perform action on a realized surface Yes yes yes

1. The documented API is broken

new window, new tab and split return objects with the correct structure - count of tabs and count of terminals are exactly what was asked for - but no shell process is ever spawned. Nine probe windows were created across the session; the count of login ... exec -l /bin/zsh processes stayed at 29 throughout. The windows are visible and blank, and the tabs carry the placeholder ghost emoji as their name.

Any subsequent call that needs the process fails:

execution error: Ghostty got an error: Terminal surface model is not
available. (-10000)

This is issue #12730, "AppleScript: new tab via 'new tab' creates empty tab without shell process (1.3.1 regression)", closed as not planned with no maintainer comment. The independent measurement here matched the report on every detail including the ghost emoji and the full set of failed workarounds (delays up to 4s, explicit focus, explicit activate, every surface-configuration variant).

The likely cause is stated in the issue and is consistent with the 1.3.1 release notes: the fix for issue #11457 (AppleScript implicitly activating the app) appears to have removed the step that also initialized the shell.

Consequence: surface configuration is unusable. Its four useful properties

  • initial working directory, command, initial input, environment variables - all ride the broken creation path. None of them had any measurable effect: no process, no file written, no directory set.

2. open -na works and is still the wrong tool

open -na Ghostty.app --args --working-directory=... produced 26 live shells in the correct 13-tab / 2-pane layout. It does this by launching a second Ghostty instance (pid 80818 alongside the operator's pid 519) which then restores the saved session, because window-save-state = always.

That makes it useless for a day plan on two counts: it duplicates the entire existing workspace rather than opening a chosen subset, and session restore restores layout and working directories but never running programs - the panes came up as bare shells with no Claude Code. It also leaves the operator with two instances of their whole environment.

3. The path that works: actions on realized surfaces

The AppleScript creation commands are broken. The AppleScript action mechanism is not, and new_tab / new_split are both in ghostty +list-actions. Performing them on a surface that is already realized produces surfaces that are themselves realized:

perform action "new_tab" on <realized terminal>      -> +1 live shell
perform action "new_split:right" on <realized term>  -> +1 live shell

Measured: 29 -> 31 shells, tab count 13 -> 14, and the operator confirmed a live prompt in the new tab.

input text + send key "enter" then works on those panes. Two panes were driven to cd into different repositories and report pwd plus their own PID; both wrote distinct directories and distinct PIDs. This is the full chain, and it needs no surface configuration at all - the working directory becomes a typed cd, and the command becomes typed text.

Three constraints any implementation inherits

These came out of the measurement and are not negotiable design details.

  1. Never target a pane by position. last tab of w is not the tab that was just created; new tabs are inserted relative to the current tab, not appended. A probe that trusted position typed into a tab it did not create. The new tab must be captured by diffing tab ids before and after. This is the failure mode with the worst blast radius: the text lands in whatever program that pane is running, which may be another repo's live Claude Code session.
  2. A new tab inherits the anchor pane's working directory (working-directory = inherit), so every pane must be cd-ed explicitly. Inheriting silently produces a plan that looks right and points at the wrong repo.
  3. count of terminals is not a reliable pane count. Probe tabs reported 3 and 4 terminal objects where 2 shells existed. Panes must be addressed by the ids captured at creation, never enumerated after the fact.

Recommendation

Both paths, split across two homes - and the split is the recommendation, not a hedge.

  • The plan itself - which repos, in what order, with which startup command - belongs here, as a third rendering of the scan board.sh already performs, built with exactly the argument --brief was: it is a lookup over data the scan already holds, it costs zero model calls, and route.sh already derives the per-repo command. Printing that plan as pasteable commands is path (a), and it is complete on its own.
  • Driving Ghostty belongs in a separate repo that consumes the plan. The measurement is the argument: this is a version-pinned, undocumented composition on top of an API the maintainer calls a preview and expects to break in 1.4, whose documented path is already broken and whose regression was closed as not planned. That is a maintenance surface with its own release cadence, its own failure modes, and a blast radius that reaches into other repos' live sessions. None of that is mailbox transport, and none of it should be able to break coord-inbox or board. That board.sh lives here is not an argument for Ghostty automation living here.

The dependency runs one way: the driver consumes the plan, the plan never knows a terminal exists. If 1.4 breaks the driver, the day plan still prints and the operator still pastes.

Not measured

  • Whether input text is safe against a pane running a full-screen TUI.
  • Whether 1.3.0 is unaffected. The issue calls this a 1.3.1 regression, which implies it, but downgrading a daily driver to gain a preview API that changes in 1.4 was not evaluated as a serious option.
  • How "focus" should be expressed, how many tabs a day plan should propose, and whether it should remember yesterday. These were deferred until the gate question was answered; they are now open.