Files
orca/src/main/startup/shell-path-probe.ts
T
Neil 47e8985117 fix(serve): keep orca serve running while it probes the login shell (#38515)
* fix(serve): keep `orca serve` running while it probes the login shell

`orca serve` started from an interactive terminal stopped itself ("[1]+
Stopped") before printing its pairing link. orcad inherits the terminal
and probes the login shell with `$SHELL -ilc`; an interactive bash 5 or
zsh takes the terminal's foreground job, so orcad's next terminal access
raised SIGTTOU/SIGTTIN. Give both shell probes (login environment and
PATH) a POSIX session of their own so they never hold a controlling
terminal. Windows keeps attached spawns, where `detached` would open a
console.

Refs #38347

* fix(serve): end a detached shell probe with its owner and on timeout

A probe in its own session no longer receives the terminal's Ctrl+C or
hangup, so a hung rc could outlive `orca serve`. Kill the probe's process
group by its own pid when it times out and when its owner exits while it
still runs; orcad and Electron turn INT/TERM into an exit, so a Ctrl+C
reaches it that way. No signal listeners: one would replace Electron's
graceful signal quit. The timeout now also ends what the rc started, not
just the shell.

Refs #38347
2026-11-05 16:28:20 -08:00

51 lines
2.2 KiB
TypeScript

import { delimiter, win32 as pathWin32 } from 'node:path'
import { resolveWindowsShellStartupFamily } from '../../shared/windows-terminal-shell'
export const SHELL_PATH_DELIMITER = '__ORCA_SHELL_PATH__'
/** Own session on POSIX so an `-i` probe can't take the launching terminal's foreground; Windows `detached` opens a console. */
export function shellProbeDetached(): boolean {
return process.platform !== 'win32'
}
/** Build the login PATH probe in the selected shell's native syntax. */
export function shellPathProbe(shell: string): {
args: string[]
pathDelimiter: string
detached: boolean
} {
return { ...shellPathProbeCommand(shell), detached: shellProbeDetached() }
}
function shellPathProbeCommand(shell: string): { args: string[]; pathDelimiter: string } {
if (process.platform !== 'win32') {
if (pathWin32.basename(shell).toLowerCase() === 'nu') {
// Nushell stores PATH as a list; POSIX quoting would print a literal.
const command = [
`print -n '${SHELL_PATH_DELIMITER}';`,
'print -n ($env.PATH | str join (char esep));',
`print -n '${SHELL_PATH_DELIMITER}'`
].join(' ')
// Retain interactive login flags for $nu.is-interactive rc behavior.
return { args: ['-ilc', command], pathDelimiter: delimiter }
}
const command =
`printf '%s' '${SHELL_PATH_DELIMITER}'; printf '%s' "$PATH"; ` +
`printf '%s' '${SHELL_PATH_DELIMITER}'`
return { args: ['-ilc', command], pathDelimiter: delimiter }
}
if (resolveWindowsShellStartupFamily(shell) === 'posix') {
// Native children cannot resolve Git Bash /c/... PATH entries.
const command =
`printf '%s' '${SHELL_PATH_DELIMITER}'; cygpath -wp "$PATH"; ` +
`printf '%s' '${SHELL_PATH_DELIMITER}'`
return { args: ['-ilc', command], pathDelimiter: ';' }
}
const command =
`[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false); ` +
`[Console]::Write('${SHELL_PATH_DELIMITER}'); [Console]::Write($env:Path); ` +
`[Console]::Write('${SHELL_PATH_DELIMITER}')`
// Load profiles to capture user configuration; do not pass NoProfile.
return { args: ['-NoLogo', '-Command', command], pathDelimiter: ';' }
}