SuperAPI AI Directory

ghostty-use

Agents & models

View repository

Updated 10-15 20:34

About this skill

ghostty-use

Capture identifiable Claude Code and Codex terminal sessions before a reboot and reopen recorded IDs afterwards. Manual snapshots grade liveness; automatic backups store recovery metadata with explicit partial coverage and unknown liveness.

Ghostty on macOS has no session-restoration CLI or AppleScript tab dictionary (verified on Ghostty 1.3.1, 2026-10-04), so this skill walks a practical loop around that limit: manual ps inventory → liveness-graded snapshot → keystroke-paste restore → automatic reconciliation that makes every paste failure visible.

Entry decision tree

You just said / want Run
Quitting for an update / reboot ("save sessions before restart") snapshot
Periodic backup, save only changes ("regular incremental snapshots") Read automatic-snapshots.md, then ghostty_watch.py install --apply
Rebooted, want tabs back ("restore previous windows") Use a suitable snapshot; if absent or outdated, reconstruct, then restore --all on the selected manifest
"Was anything lost?" / suspicion after restore check
One specific session to bring back restore --only <id-prefix>
Codex quota running out; manually switching account, keeping all Codex work Read codex-account-switch.md; switch-prepare → manual exit/login → switch-restore

Codex account switch

Preserve all Ghostty Codex sessions before the user manually switches ChatGPT accounts, then reopen the original session IDs in one batch. Exclude Claude Code. Read codex-account-switch.md before this route. Use scripts/ghostty_session.py switch-prepare and switch-restore; the bundled scripts/ghostty_switch.py owns fixed-manifest selection, account/process checks, duplicate-safe retries and separate authentication reporting.

python3 <skill-dir>/scripts/ghostty_session.py switch-prepare
# User: finish work, exit the saved Codex TUIs and manually switch account.
python3 <skill-dir>/scripts/ghostty_session.py switch-restore

Default to tabs in one window; use --layout windows only when independent windows are requested. Keep the printed fixed manifest through the handoff. Treat unresolved capture, unchanged account or still-running old processes as an incomplete handoff. Never terminate processes or switch credentials through this route. Ask for a bounded Ghostty keyboard/mouse handoff before actual opening; --dry-run and --check remain read-only. Distinguish reopened IDs from new-account requests that worked: the script reports authentication as not_checked and sends no test prompt.

Quick start

# Before quitting (from any directory):
python3 <skill-dir>/scripts/ghostty_session.py snapshot

# After restart (from any directory; needs Accessibility permission for keystrokes):
python3 <skill-dir>/scripts/ghostty_session.py restore        # manual: active only; automatic: full recorded set
python3 <skill-dir>/scripts/ghostty_session.py restore --all  # include waiting/quota/dead/stale/unknown records

The snapshot lands in ~/.ghostty-session/snapshots/ (home-relative, survives reboot). The report accounts for every live TUI: captured sessions plus an unresolved section for fresh tabs that have no transcript yet — a session count lower than the tab count is explained there, not silently lost. restore skips already-live IDs by default. It finishes with a bounded auto-check that prints N/M present, a manual reopen command and a retry command containing only missing IDs. Read that result before declaring success; per-tab SENT (unverified) is a paste acknowledgement, not proof that a session reopened. Re-running the same manifest opens only the IDs still missing. --dry-run prints commands without GUI actions.

Automatic change-only backups

Read automatic-snapshots.md before installing or operating scripts/ghostty_watch.py. It saves only changed restore state and keeps all prior versions. Unchanged rounds write no snapshot or routine log. Automatic manifests cover Ghostty process descendants without transcript reads; their liveness is explicitly unknown and restore selects the whole recorded set. UUID-less TUIs remain unresolved in automatic manifests — the watcher's bounded scan deliberately reads no transcripts; manual snapshot/check/restore resolve them (see the anchor bullet below). Partial observations preserve prior missing IDs conservatively. The watcher never opens apps or tabs. Manual snapshots keep their active-only restore default.

No suitable snapshot: reconstruct the recovery manifest

For Codex identity and indexed recovery, use the maintained daymade-claude-code:read-codex-history dependency. Resolve that Skill's directory from the current host; install its daymade-claude-code plugin through the host's ordinary marketplace flow when authorized and absent. The script discovers a source checkout or a separately installed sibling read-codex-history; other layouts use --history-reader <reader-skill-dir> or GHOSTTY_CODEX_HISTORY_READER. Missing reader or state index fails explicitly; do not replace it with a rollout corpus scan.

# Review candidates without writing state or opening tabs:
python3 <skill-dir>/scripts/ghostty_session.py reconstruct \
  --history-reader <reader-skill-dir> --recent-hours 72 --limit 100 --dry-run

# Build one chosen set in a NEW file; --only accepts unambiguous ID prefixes:
python3 <skill-dir>/scripts/ghostty_session.py reconstruct \
  --history-reader <reader-skill-dir> --since <YYYY-MM-DD> \
  --only <id-prefix-1> <id-prefix-2> --out <recovery-manifest.json>

# Restore the full selected set in one invocation, including waiting/quota rows:
python3 <skill-dir>/scripts/ghostty_session.py restore \
  --snapshot <recovery-manifest.json> --all

Reconstruction merges the optional --snapshot (otherwise latest, if present) with bounded recent indexed Codex candidates. It verifies internal rollout identity using the owning reader and marks each row past-snapshot or indexed-terminal-candidate. Terminal evidence is source=cli or originator=codex-tui; actual TUIs can report source=vscode. --terminal-source and --terminal-originator customize those accepted metadata values. Subagents are excluded by the owning inventory's metadata, not by underscores in filenames. --codex-home selects a nondefault Codex store.

Past membership proves only that a session appeared in that snapshot. Indexed terminal candidates prove terminal origin, not that the tab was live at shutdown. Review timestamps/titles and select the intended set before restore. --limit is a hard cap on indexed candidates inspected; widen discovery bounds only when needed. No snapshot means Claude membership remains unknown; reconstruction does not discover Claude sessions. Preserve explicit Claude records from a known snapshot rather than claiming an exhaustive recovery.

The new manifest never changes snapshots/latest.json and refuses to overwrite an existing output. Exit 0 means the selected evidence was verified; exit 1 means the manifest lists rejected candidates requiring attention; exit 2 means invalid input or unavailable evidence. Dry-run output is JSON for inspection.

What manual snapshot records per session

  • Anchor: the session UUID from the process command line when present (never match on process names — argv[0] flips between bare claude and /usr/local/bin/claude, and name matching produced two false "all sessions gone" reports on 2026-10-04). Fresh TUIs carry no UUID on argv (only resume/fork writes one), so they are anchored from transcript storage instead: the session file born at or after the process started, in the project bucket of the same cwd, internal identity verified (2026-10-07: 11 of 25 live sessions were fresh TUIs, invisible to argv-only matching). When several fresh TUIs share one bucket, transcripts are assigned disjointly — one file per TUI; an undecidable race (equidistant claims) refuses to the unresolved section rather than guessing. Live TUIs matching neither way are printed as unresolved rows in the snapshot report — visible, never silently dropped.
  • Liveness: last real interaction time read from the content of the session file — not the file mtime (idle TUIs keep touching files; on 2026-10-04 an "active this afternoon" read was contradicted by in-file timestamps showing death at 03:21).
  • Channel health: dead-channel when a Claude transcript's tail carries a structured isApiErrorMessage with "Login expired" — restoring such a tab reopens history but the session stops at /login; the snapshot marks it so you can skip it. Prose inside a conversation that merely discusses an error never counts (a healthy session that once talked about "Login expired" was misclassified before this rule).
  • Profile: parsed from --settings .../settings/<name>.json when present, else direct.

Restore mechanics and limits

  • Reopen = activate Ghostty → Cmd+T → clipboard-paste the reopen command → Return, one tab per missing session, followed by mandatory auto-reconciliation. Keystroke paste is timing-sensitive: an interruption between Cmd+T and the paste leaves an empty tab whose command was silently lost (measured 2026-10-04). The auto-check exists to make that visible; never skip it, and never declare success from paste return codes alone.
  • Before any paste, tell the user to leave keyboard and mouse untouched through reconciliation. The script repeats this notice before its first GUI action.
  • Requires Accessibility permission for osascript keystrokes (System Events). If the current host denies Ghostty UI automation, do not switch channels to bypass it: prepare the selected manifest and exact user-run restore command, then use read-only check --snapshot <manifest> --strict after the user runs it.
  • Window grouping is not restorable — Ghostty's macOS accessibility surface exposes no tab→window mapping. All tabs reopen into one window; reorder manually if needed.
  • Rollout files that codex itself cannot find anymore print no-artifact; restoring them replays the same codex resume <id> and stays a best effort.

Profile environment mapping (optional)

If your Claude profiles inject config via environment (e.g. CLAUDE_CONFIG_DIR), record prefixes in ~/.ghostty-session/profile-env.json so restored tabs boot the same profile context:

{"research": "CLAUDE_CONFIG_DIR=~/.claude-profiles/research", "family": "HTTP_PROXY=http://127.0.0.1:7890"}

Without it, restores replay captured settings, profile and permission/model flags; process environment beyond argv remains unknown. The mapping is user-local data outside the skill bundle; no default mapping ships.

Troubleshooting

Symptom Cause / fix
restore reports STILL MISSING run the printed missing-only retry, or the exact reopen command by hand in a new tab
no-artifact on a Codex session the verified indexed session has no usable rollout; resume remains best-effort
unknown Codex liveness reader/index unavailable or identity ambiguous; resolve the dependency/store instead of choosing the first filename match
dead-channel sessions restored anyway expected: history reopens, session stops at /login; re-auth or switch provider
check says present but tab looks empty another live process already claimed that session id (e.g. resumed in another tab)
osascript refuses keystrokes grant Accessibility (System Events) permission; verify with osascript -e 'tell application "System Events" to get UI elements enabled'
a brand-new tab is missing from the session list a fresh TUI has no argv UUID and its transcript file appears with its first message; until then it shows under unresolved live TUIs. If the first message came >20 minutes after the tab opened, the birth window has passed and the tab stays unresolved by design

Deeper details

  • references/session_liveness.md — storage layouts (Claude projects / Codex sessions), transcript structures, resume-fork naming, liveness rules and the misclassification cases behind them. Read when a liveness result looks wrong or layouts changed after an app update.
  • references/restore_mechanics.md — why Ghostty offers no CLI/AX restore path, the keystroke-paste protocol, reconciliation design, and the window-grouping boundary. Read before modifying the restore path.