From 71bf7288e0d32c7f15f41bb9d5ba103f00747a8d Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Thu, 22 Oct 2026 19:03:04 +0200 Subject: [PATCH] fix(remote): leave the remote-history PATH bootstrap out of this PR Merge-time fix for #645: the PATH bootstrap for remote history reads (src/remote-pane-history.ts, src/remote-hosts.ts and its test) is a separate fix from the scroll work. On its own it does not reach a host whose tmux lives only under /opt/homebrew/bin, because the link precheck, the launch, the alive probe and discovery still run a bare tmux through the same non-interactive ssh shell. It belongs in a follow-up PR that routes every remote tmux call through one wrapper. These files are restored to the PR's merge base; every scroll change stays. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/remote-hosts.ts | 8 ------- src/remote-pane-history.ts | 13 ++---------- test/remote-pane-history.test.ts | 36 +------------------------------- 3 files changed, 3 insertions(+), 54 deletions(-) diff --git a/src/remote-hosts.ts b/src/remote-hosts.ts index 3a68aa29..3dea3bde 100644 --- a/src/remote-hosts.ts +++ b/src/remote-hosts.ts @@ -89,14 +89,6 @@ export function remoteLoginShellCommand(command: string): string { return `exec ${REMOTE_LOGIN_SHELL} -i -l -c ${shellescape(command)}`; } -/** Standard CLI locations missing from non-interactive SSH shells (notably Homebrew). */ -export const REMOTE_PATH_BOOTSTRAP = - 'export PATH=$HOME/.opencode/bin:$HOME/.local/bin:$HOME/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH'; - -export function withRemotePathBootstrap(command: string): string { - return `${REMOTE_PATH_BOOTSTRAP}; ${command}`; -} - /** * The CLI text a location overlay should launch for `mode`, or null when this build has no * entry for it. `overlays..command` when the entry names one, otherwise the bare diff --git a/src/remote-pane-history.ts b/src/remote-pane-history.ts index b3eab6b0..f4a12e48 100644 --- a/src/remote-pane-history.ts +++ b/src/remote-pane-history.ts @@ -48,13 +48,7 @@ import { exec } from 'node:child_process'; import { randomBytes } from 'node:crypto'; import { promisify } from 'node:util'; import type { SessionRemote } from './types/session.js'; -import { - buildSshConnectionArgs, - classifyRemoteAliveExit, - remoteSshTarget, - shellescape, - withRemotePathBootstrap, -} from './remote-hosts.js'; +import { buildSshConnectionArgs, classifyRemoteAliveExit, remoteSshTarget, shellescape } from './remote-hosts.js'; import { RemoteSshSlotTimeoutError, runWithRemoteSshLimit } from './remote-ssh-limiter.js'; import { remoteTmuxLocation } from './tmux-manager.js'; @@ -159,10 +153,7 @@ export function buildRemoteScrollbackCommand( marker: string ): string { const { socket, sessionName } = remoteTmuxLocation(remote, sessionId); - // Non-interactive macOS shells omit Homebrew and other per-user CLI paths. - const script = withRemotePathBootstrap( - buildRemoteScrollbackScript(socket, sessionName, historyLines, tailBytes, marker) - ); + const script = buildRemoteScrollbackScript(socket, sessionName, historyLines, tailBytes, marker); // `exec` so a timeout kill reaches ssh itself, not only the local `sh -c`. return ['exec', ...buildSshConnectionArgs(remote), remoteSshTarget(remote), shellescape(script)].join(' '); } diff --git a/test/remote-pane-history.test.ts b/test/remote-pane-history.test.ts index 03f9aa77..014e7f48 100644 --- a/test/remote-pane-history.test.ts +++ b/test/remote-pane-history.test.ts @@ -11,7 +11,7 @@ import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, vi } from 'vitest'; import { execFileSync } from 'node:child_process'; -import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { @@ -79,40 +79,6 @@ describe('buildRemoteScrollbackCommand', () => { }); }); -describe('remote history PATH bootstrap', () => { - it('finds tmux installed under the remote home when the login shell omits it', () => { - const root = mkdtempSync(join(tmpdir(), 'remote-history-path-')); - const home = join(root, 'remote home'); - const sshBin = join(root, 'local-bin'); - const remoteBin = join(home, '.local', 'bin'); - mkdirSync(sshBin, { recursive: true }); - mkdirSync(remoteBin, { recursive: true }); - writeFileSync(join(sshBin, 'ssh'), '#!/bin/sh\nfor arg do command="$arg"; done\nexec /bin/sh -c "$command"\n', { - mode: 0o755, - }); - writeFileSync( - join(remoteBin, 'tmux'), - `#!/bin/sh -printf 'older row\\n${MARKER}\\n1\\ncurrent screen\\n' -`, - { mode: 0o755 } - ); - try { - const command = buildRemoteScrollbackCommand(remote, SESSION_ID, 100, 0, MARKER); - const stdout = execFileSync('/bin/sh', ['-c', command], { - encoding: 'utf-8', - env: { ...process.env, HOME: home, PATH: `${sshBin}:/usr/bin:/bin` }, - }); - expect(parseRemoteScrollbackOutput(stdout, MARKER, 0)).toEqual({ - scrollback: 'older row\n', - screen: 'current screen\n', - }); - } finally { - rmSync(root, { recursive: true, force: true }); - } - }); -}); - describe('remoteTailBytes', () => { it('adds room for the marker, size and screen to the cap, so a cut lands in the history', () => { expect(remoteTailBytes(2048)).toBe(2048 + 1024 * 1024);