fix: set TERM and use login shell for interactive SSH sessions (#1754)

SSH interactive sessions ran the agent command in a non-login,
non-interactive shell — .bashrc/.profile weren't sourced and TERM
wasn't always set, making the shell feel broken (no colors, bad
line editing, missing env).

Fix for all 6 SSH-based clouds (DO, Hetzner, AWS, GCP, Fly, Daytona):
- Forward local TERM (default xterm-256color) to the remote
- Use `exec bash -l -c` for a proper login shell

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
A 2026-02-22 14:14:13 -08:00 committed by GitHub
parent a67b9c7a75
commit 7b021fb1f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 13 additions and 7 deletions

View file

@ -745,7 +745,8 @@ export async function uploadFile(localPath: string, remotePath: string): Promise
}
export async function interactiveSession(cmd: string): Promise<number> {
const fullCmd = `export PATH="$HOME/.npm-global/bin:$HOME/.claude/local/bin:$HOME/.local/bin:$HOME/.bun/bin:$PATH" && ${cmd}`;
const term = process.env.TERM || "xterm-256color";
const fullCmd = `export TERM=${term} PATH="$HOME/.npm-global/bin:$HOME/.claude/local/bin:$HOME/.local/bin:$HOME/.bun/bin:$PATH" && exec bash -l -c ${JSON.stringify(cmd)}`;
const escapedCmd = fullCmd.replace(/'/g, "'\\''");
const proc = Bun.spawn(
["ssh", ...SSH_OPTS, "-t", `${SSH_USER}@${instanceIp}`, `bash -c '${escapedCmd}'`],

View file

@ -423,7 +423,8 @@ export async function uploadFile(
}
export async function interactiveSession(cmd: string): Promise<number> {
const fullCmd = `export PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH" && ${cmd}`;
const term = process.env.TERM || "xterm-256color";
const fullCmd = `export TERM=${term} PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH" && exec bash -l -c ${JSON.stringify(cmd)}`;
// Interactive mode — drop BatchMode so the PTY works
const args = [

View file

@ -884,7 +884,8 @@ export async function interactiveSession(
ip?: string,
): Promise<number> {
const serverIp = ip || doServerIp;
const fullCmd = `export PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH" && ${cmd}`;
const term = process.env.TERM || "xterm-256color";
const fullCmd = `export TERM=${term} PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH" && exec bash -l -c ${JSON.stringify(cmd)}`;
const proc = Bun.spawn(
["ssh", ...SSH_OPTS, "-t", `root@${serverIp}`, fullCmd],

View file

@ -795,7 +795,8 @@ export async function uploadFile(
}
export async function interactiveSession(cmd: string): Promise<number> {
const fullCmd = `export PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH" && ${cmd}`;
const term = process.env.TERM || "xterm-256color";
const fullCmd = `export TERM=${term} PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH" && exec bash -l -c ${JSON.stringify(cmd)}`;
// Shell-quote the command for -C
const escapedCmd = fullCmd.replace(/'/g, "'\\''");
const flyCmd = getCmd()!;

View file

@ -651,7 +651,8 @@ export async function uploadFile(localPath: string, remotePath: string): Promise
export async function interactiveSession(cmd: string): Promise<number> {
const username = resolveUsername();
const fullCmd = `export PATH="$HOME/.npm-global/bin:$HOME/.claude/local/bin:$HOME/.local/bin:$HOME/.bun/bin:$PATH" && ${cmd}`;
const term = process.env.TERM || "xterm-256color";
const fullCmd = `export TERM=${term} PATH="$HOME/.npm-global/bin:$HOME/.claude/local/bin:$HOME/.local/bin:$HOME/.bun/bin:$PATH" && exec bash -l -c ${JSON.stringify(cmd)}`;
const proc = Bun.spawn(
["ssh", ...SSH_OPTS.split(" "), "-t", `${username}@${gcpServerIp}`, `bash -c ${shellQuote(fullCmd)}`],

View file

@ -515,7 +515,8 @@ export async function interactiveSession(
ip?: string,
): Promise<number> {
const serverIp = ip || hetznerServerIp;
const fullCmd = `export PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH" && ${cmd}`;
const term = process.env.TERM || "xterm-256color";
const fullCmd = `export TERM=${term} PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH" && exec bash -l -c ${JSON.stringify(cmd)}`;
const proc = Bun.spawn(
["ssh", ...SSH_OPTS, "-t", `root@${serverIp}`, fullCmd],