Skip to content

Fix Ghostty SSH wrapper path in embedded app bundles - #8109

Merged
austinywang merged 2 commits into
mainfrom
issue-8093-ssh-ghostty-binary-path
Jul 15, 2026
Merged

austinywang merged 2 commits into
mainfrom
issue-8093-ssh-ghostty-binary-path

Conversation

@austinywang

@austinywang austinywang commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • export the exact bundled Ghostty CLI helper path from cmux and protect it from inherited/user environment overrides
  • teach Ghostty core and its zsh, bash, fish, nushell, and elvish integrations to invoke that full path instead of reconstructing <GUI executable dir>/ghostty
  • publish and checksum-pin the matching universal ReleaseFast GhosttyKit

Root cause

Ghostty derived GHOSTTY_BIN_DIR from selfExePath() and every SSH shell wrapper appended /ghostty. In a GhosttyKit host, selfExePath() is cmux, so the wrapper constructed /Applications/cmux.app/Contents/MacOS/ghostty. cmux actually ships the Ghostty CLI helper at Contents/Resources/bin/ghostty.

The fix introduces one full executable-path contract, GHOSTTY_BIN. Native Ghostty resolves it to its own executable; cmux sets it to its bundled helper. GHOSTTY_BIN_DIR remains only the directory used by the independent PATH feature. Embedded hosts without a helper do not install a broken SSH wrapper.

Affected users

Ghostty defaults ssh-env and ssh-terminfo to off, so ordinary sessions without either feature do not define the wrapper and will not reproduce the bug. It affects sessions where either feature is enabled by Ghostty config or cmux remote/bootstrap behavior. A separately installed Ghostty does not determine the outcome because the old wrapper directly executed the derived bundle path instead of searching PATH.

Regression proof

The test-only change was merged separately as #8102. Against the old submodule it invokes the actual zsh integration and fails with exit 127 and ssh:4: no such file or directory: .../cmux.app/Contents/MacOS/ghostty, matching #8093. Against this fixed head the same zsh and bash behavior checks invoke the host-provided helper as +ssh -- user@example.com and pass.

Verification

  • python3 tests/test_issue_8093_ghostty_ssh_binary_path.py
  • zsh, bash, and fish syntax checks
  • Ghostty fork required test workflow: green (shell-integration: invoke resolved Ghostty CLI path ghostty#115)
  • universal ReleaseFast GhosttyKit built from b4b6d69c8, archive validator passed, GitHub asset digest pinned as 89a2d738fc566c9c91e2c0ed7c2c9fa9f6ac18d9f0541fd9ff58e41bb8b8caa9
  • ./tests/test_ci_ghosttykit_checksum_present.sh
  • ./tests/test_ci_ghosttykit_checksum_verification.sh
  • python3 scripts/check-package-resolved-policy.py
  • python3 scripts/check-workspace-package-groups.py --check
  • global Swift budget script run; current main baseline fails in unrelated files, while the only touched Swift file is 315 lines and neither budget TSV changes
  • no local xcodebuild or app dogfood build run, per task instructions

Closes #8093


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Fixes SSH shell wrapper failures in embedded bundles by exporting the exact Ghostty CLI path via GHOSTTY_BIN, so shells call the bundled helper instead of a reconstructed path. Adds tests for fish ssh-env/ssh-terminfo variants. Fixes #8093.

  • Bug Fixes

    • Set GHOSTTY_BIN from the embedded helper at Contents/Resources/bin/ghostty when executable; protects against env overrides.
    • Update zsh, bash, fish, nushell, and elvish SSH integrations to invoke GHOSTTY_BIN; skip wrapper if no helper is provided.
    • Keep GHOSTTY_BIN_DIR only for the shell PATH feature; no longer used to rebuild a filename.
    • Add test coverage for fish SSH wrapper feature variants and expected flags.
  • Dependencies

    • Update ghostty submodule to include the CLI path contract.
    • Pin the matching GhosttyKit release checksum in scripts/ghosttykit-checksums.txt; docs updated.

Written for commit b05b779. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Improved terminal integration by exposing the bundled Ghostty executable through the managed environment when available.
    • Updated the embedded Ghostty component and pinned its corresponding release artifact.
  • Bug Fixes

    • Improved SSH shell integrations to reliably invoke the correct host-provided executable.
    • Added support and coverage for Fish shell integrations and multiple SSH feature combinations.
  • Documentation

    • Documented executable path handling, environment variable behavior, and updated embedded component references.

@vercel

vercel Bot commented Jul 15, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 15, 2026 3:51am
cmux-staging Building Building Preview, Comment Jul 15, 2026 3:51am

@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6bfce581-bbe6-46c8-9b47-700dac57a3c2

📥 Commits

Reviewing files that changed from the base of the PR and between 518a46c and b05b779.

📒 Files selected for processing (5)
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+RuntimeSurfaceCreation.swift
  • docs/ghostty-fork.md
  • ghostty
  • scripts/ghosttykit-checksums.txt
  • tests/test_issue_8093_ghostty_ssh_binary_path.py

📝 Walkthrough

Walkthrough

The Ghostty fork is updated and its CLI path contract is documented. Runtime surface creation now exports an executable GHOSTTY_BIN path when available, while SSH wrapper tests cover shell-specific feature combinations and expected arguments.

Changes

Ghostty CLI path integration

Layer / File(s) Summary
Ghostty fork contract and pinned artifacts
docs/ghostty-fork.md, ghostty, scripts/ghosttykit-checksums.txt
The Ghostty revision, GhosttyKit checksum, release references, and GHOSTTY_BIN/GHOSTTY_BIN_DIR ownership contract are updated.
Runtime CLI environment setup
Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+RuntimeSurfaceCreation.swift
Runtime surface creation derives the CLI and executable paths and sets GHOSTTY_BIN when the executable exists and is executable.
SSH wrapper path validation
tests/test_issue_8093_ghostty_ssh_binary_path.py
SSH wrapper tests use feature-specific expected arguments and add conditional Fish coverage alongside Zsh and Bash cases.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: azooz2003-bit, lawrencecchen

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-8093-ssh-ghostty-binary-path

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@austinywang
austinywang marked this pull request as ready for review July 15, 2026 03:38
@austinywang
austinywang merged commit be6ea18 into main Jul 15, 2026
6 of 9 checks passed
@greptile-apps

greptile-apps Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the Ghostty SSH wrapper failure in embedded app bundles by introducing GHOSTTY_BIN as the single authoritative env var for the exact Ghostty CLI helper path, replacing the broken pattern of reconstructing <GUI executable dir>/ghostty. The Swift surface-creation code now sets GHOSTTY_BIN only when the bundled helper is present and executable, and the updated Ghostty submodule teaches all shell integrations (zsh, bash, fish, nushell, elvish) to invoke that path directly.

  • The isExecutableFile guard is correct: when the helper is absent GHOSTTY_BIN is left unset and the Ghostty integration installs no SSH wrapper, so ordinary SSH sessions are unaffected.
  • Fish integration is newly covered by three feature-flag test variants; the fish command in the test correctly passes the integration path as $argv[1] and emits fish_prompt to trigger deferred setup.
  • The new SHA256 checksum entry in scripts/ghosttykit-checksums.txt is 64 hex characters, consistent with all prior entries.

Confidence Score: 4/5

Safe to merge; the core path fix is correct and well-tested, with one minor inconsistency in the filesystem abstraction used.

The Swift change is small, correctly guarded by an executable check, and consistent with how other bundled paths are handled in the same function except for one filesystem abstraction inconsistency. All other changed files (docs, checksums, submodule, test) look correct.

TerminalSurface+RuntimeSurfaceCreation.swift — the new GHOSTTY_BIN guard uses FileManager.default instead of the injected runtimeFilesystem used just above it.

Important Files Changed

Filename Overview
Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+RuntimeSurfaceCreation.swift Adds GHOSTTY_BIN env var set to the bundled helper path when executable; uses FileManager.default instead of the injectable runtimeFilesystem used for the adjacent cmux CLI check.
tests/test_issue_8093_ghostty_ssh_binary_path.py Adds fish SSH integration tests with three feature-flag variants (ssh-env, ssh-terminfo, both); parameterizes _run_wrapper to accept features and expected_arguments; guards fish tests with shutil.which check.
docs/ghostty-fork.md Updates pinned fork head to b4b6d69 and documents the GHOSTTY_BIN / GHOSTTY_BIN_DIR contract split; includes a conflict note for future merges.
ghostty Submodule pointer bumped from a630590 to b4b6d69, matching the documented fork head.
scripts/ghosttykit-checksums.txt Appends the checksum entry for the new submodule SHA; format (64-hex SHA256) is consistent with all prior entries.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant cmux as cmux Swift
    participant FS as runtimeFilesystem
    participant Env as Managed Env
    participant Shell as Shell integration

    cmux->>FS: isExecutableFile(Contents/Resources/bin/ghostty)
    alt helper executable
        FS-->>cmux: true
        cmux->>Env: "GHOSTTY_BIN = bundled helper path"
        Env->>Shell: "ssh wrapper calls exec GHOSTTY_BIN +ssh -- user@host"
    else helper absent
        FS-->>cmux: false
        cmux->>Env: GHOSTTY_BIN not set
        Env->>Shell: no SSH wrapper installed
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant cmux as cmux Swift
    participant FS as runtimeFilesystem
    participant Env as Managed Env
    participant Shell as Shell integration

    cmux->>FS: isExecutableFile(Contents/Resources/bin/ghostty)
    alt helper executable
        FS-->>cmux: true
        cmux->>Env: "GHOSTTY_BIN = bundled helper path"
        Env->>Shell: "ssh wrapper calls exec GHOSTTY_BIN +ssh -- user@host"
    else helper absent
        FS-->>cmux: false
        cmux->>Env: GHOSTTY_BIN not set
        Env->>Shell: no SSH wrapper installed
    end
Loading

Reviews (1): Last reviewed commit: "test: cover Ghostty fish SSH feature var..." | Re-trigger Greptile

Comment on lines +160 to +162
if FileManager.default.isExecutableFile(atPath: ghosttyCLIPath) {
setManagedEnvironmentValue("GHOSTTY_BIN", ghosttyCLIPath)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The adjacent CMUX_BUNDLED_CLI_PATH guard (a few lines earlier) uses the injected runtimeFilesystem.isExecutableFile() abstraction for the same kind of check. The new GHOSTTY_BIN guard goes directly to FileManager.default, bypassing the injectable seam and making this branch untestable through the same mechanism the rest of the function uses.

Suggested change
if FileManager.default.isExecutableFile(atPath: ghosttyCLIPath) {
setManagedEnvironmentValue("GHOSTTY_BIN", ghosttyCLIPath)
}
if runtimeFilesystem.isExecutableFile(ghosttyCLIPath) {
setManagedEnvironmentValue("GHOSTTY_BIN", ghosttyCLIPath)
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

This branch was successfully deployed

1 active deployment
Preview – cmux — b05b7792 Deployed Jul 15, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ghostty binary missing from application bundle?

1 participant