Skip to content

Add SSH Integration Configuration Option #7608

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 40 commits into
base: main
Choose a base branch
from

Conversation

jasonrayne
Copy link

@jasonrayne jasonrayne commented Jun 16, 2025

Addresses #4156 and #5892, specifically by implementing @mitchellh's request for "opt-in shell integration".

Problem

Ghostty's custom xterm-ghostty TERM value breaks terminal functionality when SSHing to remote systems that lack the corresponding terminfo entry. This affects enterprise environments, legacy servers, and ephemeral instances.

Solution

Adds two independent SSH integration flags within the existing shell-integration-features configuration:

shell-integration-features = ssh-env,ssh-terminfo
  • ssh-env: TERM compatibility fix (xterm-ghostty → xterm-256color) + environment variable propagation (COLORTERM, TERM_PROGRAM, TERM_PROGRAM_VERSION)
  • ssh-terminfo: Automatic terminfo installation on remote hosts with caching and utility commands

Flags work independently and harmoniously when combined, allowing users granular control over SSH integration behavior.

Implementation

Adds SSH wrapper functions across bash, zsh, fish, and elvish that handle TERM compatibility, environment propagation, and terminfo installation. Follows the same pattern as existing shell integration features - client-side only with graceful fallback behavior.

The flag-based approach allows users to choose exactly the features they need:

  • Environment compatibility only: ssh-env
  • Terminfo installation only: ssh-terminfo
  • Optimal experience: ssh-env,ssh-terminfo
  • No SSH integration: omit both flags

Evolution Note

Based on maintainer feedback, this evolved from a progressive enhancement approach (ssh-integration = basic | full) to independent flags within shell-integration-features. See discussion below for the complete architectural evolution and rationale.

@jasonrayne jasonrayne requested review from a team as code owners June 16, 2025 20:53
@pluiedev
Copy link
Member

Also I think we should consider an approach that might be less intrusive for the user. I find how Kitty does it to be pretty ingenious, but since it's GPL code we can't look at it exactly. We can definitely still try to piece it together just through the broad-picture description, though.

@jasonrayne
Copy link
Author

Also I think we should consider an approach that might be less intrusive for the user. I find how Kitty does it to be pretty ingenious, but since it's GPL code we can't look at it exactly. We can definitely still try to piece it together just through the broad-picture description, though.

@pluiedev I completely agree about Kitty's approach. I see this PR as an interim solution to address the immediate pain points people are hitting (terminfo compatibility, basic env propagation) while a more comprehensive solution gets designed and implemented.

Something like Ghostty's equivalent of kitten ssh would definitely be the long-term answer, but that's a much larger architectural effort. This gives users something functional now while we work toward the better solution.

@jcollie
Copy link
Member

jcollie commented Jun 16, 2025

@pluiedev I completely agree about Kitty's approach. I see this PR as an interim solution to address the immediate pain points people are hitting (terminfo compatibility, basic env propagation) while a more comprehensive solution gets designed and implemented.

Something like Ghostty's equivalent of kitten ssh would definitely be the long-term answer, but that's a much larger architectural effort. This gives users something functional now while we work toward the better solution.

I'm against including this as a part of the main config/executable. kitten ssh is the way to go. We already have the ability to run alternate "main" functions, so it would be easy to do this as ghostty +ssh. No messing around with shell integrations or anything like that.

@jparise
Copy link
Collaborator

jparise commented Jun 17, 2025

I'm against including this as a part of the main config/executable. kitten ssh is the way to go. We already have the ability to run alternate "main" functions, so it would be easy to do this as ghostty +ssh. No messing around with shell integrations or anything like that.

I haven't decided which approach I prefer, but I also see advantages in the ghostty +ssh-style approach. I elaborated on that a bit in the discussion.

... and then we could just have another shell integration feature flag (ssh) that automatically aliases ssh to ghostty +ssh.

@jasonrayne
Copy link
Author

I'm against including this as a part of the main config/executable. kitten ssh is the way to go. We already have the ability to run alternate "main" functions, so it would be easy to do this as ghostty +ssh. No messing around with shell integrations or anything like that.

I haven't decided which approach I prefer, but I also see advantages in the ghostty +ssh-style approach. I elaborated on that a bit in the discussion.

... and then we could just have another shell integration feature flag (ssh) that automatically aliases ssh to ghostty +ssh.

I personally feel that ghostty +ssh is the right approach - it's what I wanted to build initially but wasn't sure I had the Zig/project knowledge to do it justice.

@jcollie could you point me to examples of those alternate main functions? I'm willing to take a shot at it, just want to be realistic about timeline since I'm still learning both Zig and the codebase.

That said, this shell integration approach does solve the immediate pain points users are hitting today. If there's value in getting users unstuck while the proper solution gets built, I'm happy to keep iterating on this too.

@00-kat
Copy link
Contributor

00-kat commented Jun 17, 2025

could you point me to examples of those alternate main functions? I'm willing to take a shot at it, just want to be realistic about timeline since I'm still learning both Zig and the codebase.

https://github.com/ghostty-org/ghostty/tree/main/src/cli :).

Copy link
Contributor

@00-kat 00-kat left a comment

Choose a reason for hiding this comment

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

For some reason I can't test it myself (not sure why; the shell integration script doesn't seem to be sourced when I run off a git clone 🤔), but how does ssh-integration play with shell-integration = none?

Edit: and it works this time, okay then. It seems like shell-integration = none means ssh-integration is ignored, not sure if that's wanted.

Copy link
Contributor

@mitchellh mitchellh left a comment

Choose a reason for hiding this comment

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

Thanks for taking this on. Overall this is looking really good, thank you. And thank you for addressing prior feedback.

I think the only thing I'm personally hung up on is if this should be its own config or it should be part of shell-integration-features. My initial proposal you linked had augmenting the prior config in mind.

That's fairly bikeshed and I can figure it out myself. The hard work of making this work across shells you did and I love that.

@mitchellh
Copy link
Contributor

Okay had some extra time this morning so I read the other feedback.

@pluiedev @jcollie I agree that we should have a ghostty +ssh option as well, but I see this as an and and not an or. In fielding the many, many complaints about the TERM issue with SSH, I think that this solution will bridge the gap for a ton of users. Obviously, this direction was endorsed by me, so this viewpoint shouldn't be surprising.

I'd love to see a ghostty +ssh option as well (separately). And I think if we ever do the ghostty +ssh CLI, we can also possibly reimplement ssh-integration config in the shell integration to just setup aliases to that rather than the logic they're doing now.

@jasonrayne
Copy link
Author

jasonrayne commented Jun 17, 2025

Thanks for taking this on. Overall this is looking really good, thank you. And thank you for addressing prior feedback.

I think the only thing I'm personally hung up on is if this should be its own config or it should be part of shell-integration-features. My initial proposal you linked had augmenting the prior config in mind.

That's fairly bikeshed and I can figure it out myself. The hard work of making this work across shells you did and I love that.

@mitchellh You're welcome, I'm very happy to help! Thank you so much for weighing in and for the support. Really appreciate you taking the time to review this, and I'm glad the cross-shell work is useful. I'm excited about the potential for ghostty +ssh down the road as well!

Since I'm planning to switch from progressive options to flags anyway based on the extensibility feedback, I'd be happy to implement that under whichever config location you think is most appropriate. I'm actually leaning towards shell-integration-features as you originally envisioned - I only went with the standalone option because I wasn't confident enough to work with the existing config system, but that was probably the wrong call. Either approach works for me, happy to do whatever you feel is best.

@jasonrayne
Copy link
Author

For some reason I can't test it myself (not sure why; the shell integration script doesn't seem to be sourced when I run off a git clone 🤔), but how does ssh-integration play with shell-integration = none?

Try setting the resources directory explicitly for your local build:

env GHOSTTY_RESOURCES_DIR="/path/to/your/ghostty/dev/build/share/ghostty" /path/to/dev/ghostty

Good catch on the shell-integration = none, that does seem to kill ssh-integration since it currently depends on the shell-integration scripts. I'll address that as part of the upcoming flag pivot. Thanks!

@00-kat
Copy link
Contributor

00-kat commented Jun 18, 2025

Try setting the resources directory explicitly for your local build:

Actually it seems to work just fine now, I'm not sure what changed. I'm not complaining I guess ¯\_(ツ)_/¯.

@00-kat
Copy link
Contributor

00-kat commented Jun 18, 2025

Does the environment variable propagation actually work? They're not set on the remote for me.

@jasonrayne
Copy link
Author

Does the environment variable propagation actually work? They're not set on the remote for me.

Ah shoot, yeah... the environment variable propagation seems to be broken. I can confirm that TERM=xterm-ghostty is being set correctly on the remote when using ssh-integration = full, but GHOSTTY_SHELL_FEATURES isn't making it through. Investigating the issue now and will push a fix shortly.

@00-kat
Copy link
Contributor

00-kat commented Jun 18, 2025

If I remember correctly, you need to tell SSH to propagate it in your SSH configuration, and also allow it to be set in the remote's SSH configuration too. And $TERM is allowed by default.

@jasonrayne
Copy link
Author

Sorry for the commit spam.. tired brain screwed up a rebase. The actual change is in 0795681

@00-kat
Copy link
Contributor

00-kat commented Jun 19, 2025

Oh also, maybe $COLORTERM should be forwarded too, since many people are finding that some tools don't use truecolor over ssh despite Ghostty supporting it. (kitten ssh does this too from my experimentation.)

Maybe $TERM_PROGRAM and $TERM_PROGRAM_VERSION should be forwarded too, while we're at it…

@jparise
Copy link
Collaborator

jparise commented Jun 19, 2025

I think we should focus on the configuration aspect for a bit before getting deeper into the details of the implementation, which could change as a result.

I don't yet have strong feelings about using shell-integration-features versus a new ssh-integration option. I'd prefer the former out of conceptual simplicity, because all of this depends on shell integration being active, but I also see how the latter can more easily evolve to support a future ghostty +ssh use case.

Regardless of the configuration option name, I think we're talking about at least two flags (prefixed with ssh- for shell-integration-features):

  • env - forward (fixed) $TERM and other relevant environment variables
  • terminfo - do the full remote terminfo installation dance

In practice, I don't know if we need to differentiate forwarding "only (fixed) $TERM" from "$TERM+other environment variables", so I'd keep them under one case. But maybe I'm not considering some use cases?

I'm also not sure we should forward $GHOSTTY_SHELL_FEATURES at this point. That would make more sense if we add a Kitty-like "transmit the shell integration script" mechanism later under a new shell-integration flag. For now, it's only useful (and functional) if the remote shell sources its own copy of the shell integration script, which could be from a different Ghostty version.


Lastly, I don't love the amount of code that the host-level caching adds to each of the shell integration scripts. If that's truly what it takes to correctly implement this feature, then I'm much more inclined to prefer just an env-style "fix $TERM"-oriented approach in the short term and then a dash toward a ghostty +ssh implementation as the full solution.

@mitchellh
Copy link
Contributor

I agree with all of @jparise's feedback.

@jasonrayne
Copy link
Author

I think we should focus on the configuration aspect for a bit before getting deeper into the details of the implementation, which could change as a result.

Works for me! Here are my thoughts:

  • Config: Since ssh-integration currently depends on shell integration being active (fails when shell-integration = none), using shell-integration-features does make more sense to me. I'll remove the ssh-integration config from this PR entirely and can preserve that code in a separate branch for future use with ghostty +ssh.
  • Flags: I agree, ssh-env and ssh-terminfo should be sufficient for this PR.
  • Differentiation: I think we'll be ok just having ssh-env handle the essentials (TERM + COLORTERM + TERM_PROGRAM + TERM_PROGRAM_VERSION). I can't think of any use cases that would merit splitting this into separate flags, either.
  • GHOSTTY_SHELL_FEATURES: Makes total sense to drop this in favor of handling it later via ghostty +ssh - will do.
  • Terminfo/caching complexity: I understand the bloat concern. I'd like to quickly explore whether I can simplify this while still preserving the user experience. If not, I'll just fall back to the basic terminfo installation approach so we can get this shipped.

@jparise, @mitchellh, does this all sound agreeable?

- Fix elvish function name mismatch and use conj for list operations
- Simplify terminfo installation command per ghostty docs (tic -x -)
- Fix conditional structure to ensure error messages always print
- Remove redundant checks and optimize array initialization
- Use consistent patterns across bash, fish, elvish, and zsh
implementations
…tions

- Cache known hosts with terminfo in
$GHOSTTY_RESOURCES_DIR/terminfo_hosts
- Skip installation step for cached hosts (single connection instead of
two)
- Use secure file permissions (600) and atomic writes
- Extract SSH target safely from command arguments
- Maintains full functionality while improving user experience on
repeated connections
Need a sanity check on this new approach for "full" to help determine if
it's worth additional iteration/refinement.

It solves the double auth issue, successfully propagates env vars, and
avoids output noise for connections that happen after terminfo is
installed. The only issue I don't have time to fix tonight is the fact
that it drops the MOTD for cached (re)connections.
…ration-features flags

- Add ssh_env and ssh_terminfo flags to ShellIntegrationFeatures
- Remove SSHIntegration enum and ssh-integration config option
- Update setupFeatures to handle new flags via reflection
- Remove setupSSHIntegration function and all references

Integrates SSH functionality into existing shell-integration-features
system for better consistency and user control.
Rewrote shell functions to support the two new flags for
shell-integration-features:
- ssh-env: TERM compatibility + best effort environment variable
propagation (anything beyond TERM will depend on what the remote host
allows)
- ssh-terminfo: automatic terminfo installation with control socket
orchestration
- Flags work independently or combined

Implementation optimizations:
- ~65% code reduction through unified execution path
- Eliminated GHOSTTY_SSH_INTEGRATION environment variable system
- Replaced complex function dispatch with direct flag detection
- Consolidated 4 cache helper functions into single _ghst_cache()
utility
- Simplified control socket management (removed multi-step
orchestration)
- Subsequent connections to cached hosts are now directly executed and
more reliable

New additions:
- If ssh-terminfo is enabled, ghostty will be wrapped to provide users
with convenient commands to invoke either of the two utility functions:
`ghostty ssh-cache-list` and `ghostty ssh-cache-clear`
Add ssh-env and ssh-terminfo fields to existing setupFeatures tests.
Addresses feedback about separation of concerns in shell integration
scripts.

Extracts host caching logic to
`src/shell-integration/shared/ghostty-ssh-cache` and updates all four
shell integrations to use the shared script. The `shared/` subdirectory
preserves the existing organizational pattern where all shell-specific
code lives in subdirectories. This cleanly separates SSH transport logic
from cache management while reducing code duplication by ~25%.

All existing SSH integration behavior remains identical.
…o cache management

- Add +list-ssh-cache and +clear-ssh-cache CLI actions
- Remove ghostty() wrapper functions from all shell integrations
- Improve variable naming in shell scripts for readability

Addresses @00-kat's feedback about CLI discoverability and naming
consistency. The new CLI actions follow established Ghostty patterns
and are discoverable via `ghostty --help`, while maintaining clean
separation of concerns between shell logic and cache management.
Updates Config.zig documentation to reflect that SSH cache management is
now handled by proper CLI actions (+list-ssh-cache and +clear-ssh-cache)
rather than shell wrapper commands.

Fixes documentation missed in e8c8a51.
- Clarify that +list-ssh-cache shows shell integration cached hosts
- Add note about +clear-ssh-cache command and when to use it

Addresses mitchellh's feedback on action descriptions.
Clarifies this clears hosts cached by SSH shell integration, completing
mitchellh's feedback on both action descriptions.
@00-kat
Copy link
Contributor

00-kat commented Jun 25, 2025

Aside: should the Bash dependency be documented for now?

@jasonrayne
Copy link
Author

Aside: should the Bash dependency be documented for now?

Makes sense if we're going to move forward with this as an interim solution. Added a dependency note in 1873add

@00-kat
Copy link
Contributor

00-kat commented Jun 26, 2025

It might be useful to mention it in shell-integration's documentation too since that file is for developers and not users.

Also, another aside: the website's docs would also need to be updated after this is merged. (I'm just mentioning this now so that it isn't forgotten, not saying it should be done now.)

…lication in ssh_cache

- Fix fallback path from full path to "src" since full path is built
later
- Extract duplicate code from listCachedHosts and clearCache into
runCacheCommand helper
- Addresses feedback from @00-kat
infocmp is required locally to extract terminfo, tic is required on
remote hosts to install it
…n shell integration

GHOSTTY_VERSION was mistakenly referenced but is never set. Use
TERM_PROGRAM_VERSION which is actually provided by Exec.zig from
build_config.version_string.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants