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
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.
- Never target a pane by position.
last tab of wis 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 tabids 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. - A new tab inherits the anchor pane's working directory
(
working-directory = inherit), so every pane must becd-ed explicitly. Inheriting silently produces a plan that looks right and points at the wrong repo. count of terminalsis 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.shalready performs, built with exactly the argument--briefwas: it is a lookup over data the scan already holds, it costs zero model calls, androute.shalready 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-inboxorboard. Thatboard.shlives 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 textis 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.