mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
fix(terminal): keep legacy bash prompt dispatch out of OSC 133 command start after a DEBUG trap change (#29165)
On bash <= 5.0 the PROMPT_COMMAND is a single scalar string. After the user changed the DEBUG trap at the prompt (`trap - DEBUG`, `trap ':' DEBUG`), Orca's preexec never saw the status-capture command, so the legacy wrapper flag stayed unset and the dispatch tail emitted OSC 133;C before the next prompt was drawn. Set __orca_in_legacy_prompt_wrapper directly in the PROMPT_COMMAND string, and re-arm the Orca DEBUG trap at the top level of that string: bash <= 4.3 (including macOS /bin/bash 3.2) restores the caller's DEBUG trap when a function returns, so the re-arm inside __orca_finish_legacy_prompt_dispatch was undone. Tests force the legacy dispatch on any bash and also run with /bin/bash on macOS. Fixes #29164 Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
@@ -372,8 +372,6 @@ __orca_finish_legacy_prompt_dispatch() {
|
||||
eval "$__orca_suffix_part"
|
||||
done
|
||||
fi
|
||||
trap '__orca_osc133_preexec' DEBUG
|
||||
unset __orca_in_legacy_prompt_wrapper
|
||||
}
|
||||
__orca_normalize_prompt_command() {
|
||||
[[ -z "${__orca_prompt_command_normalized:-}" ]] || return 0
|
||||
@@ -395,7 +393,8 @@ __orca_normalize_prompt_command() {
|
||||
__orca_prompt_status_variable="__orca_prompt_status_$$"
|
||||
__orca_prompt_status_capture_command="$__orca_prompt_status_variable=\$?"
|
||||
__orca_prompt_status_value="\${$__orca_prompt_status_variable}"
|
||||
PROMPT_COMMAND="$__orca_prompt_status_capture_command; __orca_prompt_status=$__orca_prompt_status_value"'; __orca_prompt_had_functrace=""; if [[ -o functrace ]]; then __orca_prompt_had_functrace=1; set +T; fi; __orca_outer_debug_trap_spec="$(trap -p DEBUG)"; [[ -z "$__orca_prompt_had_functrace" ]] || set -T; unset __orca_prompt_had_functrace; __orca_run_prompt_command_array; __orca_finish_legacy_prompt_dispatch'
|
||||
# Why: a changed DEBUG trap misses the capture command, and bash <= 4.3 reverts traps set in functions.
|
||||
PROMPT_COMMAND="$__orca_prompt_status_capture_command; __orca_in_legacy_prompt_wrapper=1; __orca_prompt_status=$__orca_prompt_status_value"'; __orca_prompt_had_functrace=""; if [[ -o functrace ]]; then __orca_prompt_had_functrace=1; set +T; fi; __orca_outer_debug_trap_spec="$(trap -p DEBUG)"; [[ -z "$__orca_prompt_had_functrace" ]] || set -T; unset __orca_prompt_had_functrace; __orca_run_prompt_command_array; __orca_finish_legacy_prompt_dispatch; trap __orca_osc133_preexec DEBUG; unset __orca_in_legacy_prompt_wrapper'
|
||||
fi
|
||||
}
|
||||
__orca_prepend_prompt_command() {
|
||||
|
||||
@@ -365,8 +365,6 @@ __orca_finish_legacy_prompt_dispatch() {
|
||||
eval "$__orca_suffix_part"
|
||||
done
|
||||
fi
|
||||
trap '__orca_osc133_preexec' DEBUG
|
||||
unset __orca_in_legacy_prompt_wrapper
|
||||
}
|
||||
__orca_normalize_prompt_command() {
|
||||
[[ -z "${__orca_prompt_command_normalized:-}" ]] || return 0
|
||||
@@ -388,7 +386,8 @@ __orca_normalize_prompt_command() {
|
||||
__orca_prompt_status_variable="__orca_prompt_status_$$"
|
||||
__orca_prompt_status_capture_command="$__orca_prompt_status_variable=\$?"
|
||||
__orca_prompt_status_value="\${$__orca_prompt_status_variable}"
|
||||
PROMPT_COMMAND="$__orca_prompt_status_capture_command; __orca_prompt_status=$__orca_prompt_status_value"'; __orca_prompt_had_functrace=""; if [[ -o functrace ]]; then __orca_prompt_had_functrace=1; set +T; fi; __orca_outer_debug_trap_spec="$(trap -p DEBUG)"; [[ -z "$__orca_prompt_had_functrace" ]] || set -T; unset __orca_prompt_had_functrace; __orca_run_prompt_command_array; __orca_finish_legacy_prompt_dispatch'
|
||||
# Why: a changed DEBUG trap misses the capture command, and bash <= 4.3 reverts traps set in functions.
|
||||
PROMPT_COMMAND="$__orca_prompt_status_capture_command; __orca_in_legacy_prompt_wrapper=1; __orca_prompt_status=$__orca_prompt_status_value"'; __orca_prompt_had_functrace=""; if [[ -o functrace ]]; then __orca_prompt_had_functrace=1; set +T; fi; __orca_outer_debug_trap_spec="$(trap -p DEBUG)"; [[ -z "$__orca_prompt_had_functrace" ]] || set -T; unset __orca_prompt_had_functrace; __orca_run_prompt_command_array; __orca_finish_legacy_prompt_dispatch; trap __orca_osc133_preexec DEBUG; unset __orca_in_legacy_prompt_wrapper'
|
||||
fi
|
||||
}
|
||||
__orca_prepend_prompt_command() {
|
||||
|
||||
@@ -133,8 +133,6 @@ __orca_finish_legacy_prompt_dispatch() {
|
||||
eval "$__orca_suffix_part"
|
||||
done
|
||||
fi
|
||||
trap '__orca_osc133_preexec' DEBUG
|
||||
unset __orca_in_legacy_prompt_wrapper
|
||||
}
|
||||
__orca_normalize_prompt_command() {
|
||||
[[ -z "\${__orca_prompt_command_normalized:-}" ]] || return 0
|
||||
@@ -156,7 +154,8 @@ __orca_normalize_prompt_command() {
|
||||
__orca_prompt_status_variable="__orca_prompt_status_$$"
|
||||
__orca_prompt_status_capture_command="$__orca_prompt_status_variable=\\$?"
|
||||
__orca_prompt_status_value="\\\${$__orca_prompt_status_variable}"
|
||||
PROMPT_COMMAND="$__orca_prompt_status_capture_command; __orca_prompt_status=$__orca_prompt_status_value"'; __orca_prompt_had_functrace=""; if [[ -o functrace ]]; then __orca_prompt_had_functrace=1; set +T; fi; __orca_outer_debug_trap_spec="$(trap -p DEBUG)"; [[ -z "$__orca_prompt_had_functrace" ]] || set -T; unset __orca_prompt_had_functrace; __orca_run_prompt_command_array; __orca_finish_legacy_prompt_dispatch'
|
||||
# Why: a changed DEBUG trap misses the capture command, and bash <= 4.3 reverts traps set in functions.
|
||||
PROMPT_COMMAND="$__orca_prompt_status_capture_command; __orca_in_legacy_prompt_wrapper=1; __orca_prompt_status=$__orca_prompt_status_value"'; __orca_prompt_had_functrace=""; if [[ -o functrace ]]; then __orca_prompt_had_functrace=1; set +T; fi; __orca_outer_debug_trap_spec="$(trap -p DEBUG)"; [[ -z "$__orca_prompt_had_functrace" ]] || set -T; unset __orca_prompt_had_functrace; __orca_run_prompt_command_array; __orca_finish_legacy_prompt_dispatch; trap __orca_osc133_preexec DEBUG; unset __orca_in_legacy_prompt_wrapper'
|
||||
fi
|
||||
}
|
||||
__orca_prepend_prompt_command() {
|
||||
|
||||
@@ -13,18 +13,21 @@ const bashMajor = hasBash
|
||||
const bashMinor = hasBash
|
||||
? Number(spawnSync('bash', ['-lc', 'printf %s "${BASH_VERSINFO[1]}"']).stdout)
|
||||
: 0
|
||||
const LEGACY_GATE = 'BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 1)'
|
||||
const bashPreservesOddTerminalBackslash = bashMajor > 4 || (bashMajor === 4 && bashMinor >= 4)
|
||||
|
||||
function runInteractiveBash(
|
||||
profile: string,
|
||||
tempHome: string,
|
||||
input = 'true\nfalse\nexit 0\n'
|
||||
input = 'true\nfalse\nexit 0\n',
|
||||
rcfileContent = getDaemonBashShellReadyRcfileContent(),
|
||||
bash = 'bash'
|
||||
): string {
|
||||
const rcfile = join(tempHome, 'rcfile')
|
||||
writeFileSync(join(tempHome, '.bash_profile'), profile)
|
||||
writeFileSync(rcfile, getDaemonBashShellReadyRcfileContent())
|
||||
writeFileSync(rcfile, rcfileContent)
|
||||
const result = spawnSync(
|
||||
'bash',
|
||||
bash,
|
||||
['-lc', '"$BASH" --noprofile --rcfile "$1" -i 2>&1', 'bash', rcfile],
|
||||
{
|
||||
input,
|
||||
@@ -396,6 +399,37 @@ PROMPT_COMMAND='printf "PROMPT_REMATCH:<%s>\\n" "\${BASH_REMATCH[1]-unset}"'
|
||||
expectLifecycle(output)
|
||||
})
|
||||
|
||||
// Why: macOS /bin/bash 3.2 also reverts DEBUG traps set inside functions.
|
||||
const legacyBashes = process.platform === 'darwin' ? ['bash', '/bin/bash'] : ['bash']
|
||||
itWithBash.each(
|
||||
legacyBashes.flatMap((bash) => [
|
||||
['trap - DEBUG', bash],
|
||||
["trap ':' DEBUG", bash]
|
||||
])
|
||||
)(
|
||||
'starts the next command after its prompt when `%s` runs at a legacy prompt (%s)',
|
||||
(trapChange, bash) => {
|
||||
// Why: force the bash <= 5.0 scalar PROMPT_COMMAND dispatch on any bash.
|
||||
const rcfile = getDaemonBashShellReadyRcfileContent()
|
||||
expect(rcfile).toContain(LEGACY_GATE)
|
||||
const legacyRcfile = rcfile.replaceAll(LEGACY_GATE, '0')
|
||||
const output = runInteractiveBash(
|
||||
"PS1='ORCA_PS1> '",
|
||||
tempHome,
|
||||
`${trapChange}\ntrue\nexit 0\n`,
|
||||
legacyRcfile,
|
||||
bash
|
||||
)
|
||||
const startBeforePrompt = new RegExp(
|
||||
`${String.fromCharCode(27)}]133;A${String.fromCharCode(7)}(?:(?!ORCA_PS1> )[\\s\\S])*${String.fromCharCode(27)}]133;C`
|
||||
)
|
||||
|
||||
expect(output).toContain('ORCA_PS1> ')
|
||||
expect(output).not.toMatch(startBeforePrompt)
|
||||
expectLifecycle(output, 0)
|
||||
}
|
||||
)
|
||||
|
||||
itWithBash('does not treat user command text as a bash-preexec helper', () => {
|
||||
const output = runInteractiveBash('', tempHome, 'echo a:__bp_x\nfalse\nexit 0\n')
|
||||
|
||||
|
||||
@@ -398,6 +398,27 @@ describePosix('local PTY shell-ready launch config', () => {
|
||||
expectBashOsc133Lifecycle(output)
|
||||
})
|
||||
|
||||
itWithBash(
|
||||
'starts the next command after its prompt when the DEBUG trap is removed at a legacy prompt',
|
||||
() => {
|
||||
// Why: force the bash <= 5.0 scalar PROMPT_COMMAND dispatch on any bash.
|
||||
const gate = 'BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 1)'
|
||||
const rcfile = getBashShellReadyRcfileContent()
|
||||
expect(rcfile).toContain(gate)
|
||||
const legacyRcfile = rcfile.replaceAll(gate, '0')
|
||||
writeFileSync(join(userDataPath, '.bash_profile'), "PS1='ORCA_PS1> '\n")
|
||||
const input = 'trap - DEBUG\nfalse\nexit 0\n'
|
||||
const output = runInteractiveBashRcfile(legacyRcfile, userDataPath, input)
|
||||
const startBeforePrompt = new RegExp(
|
||||
`${String.fromCharCode(27)}]133;A${String.fromCharCode(7)}(?:(?!ORCA_PS1> )[\\s\\S])*${String.fromCharCode(27)}]133;C`
|
||||
)
|
||||
|
||||
expect(output).toContain('ORCA_PS1> ')
|
||||
expect(output).not.toMatch(startBeforePrompt)
|
||||
expectBashOsc133Lifecycle(output)
|
||||
}
|
||||
)
|
||||
|
||||
itWithBash('composes an inherited PROMPT_COMMAND ending in separators', () => {
|
||||
writeFileSync(
|
||||
join(userDataPath, '.bash_profile'),
|
||||
|
||||
Reference in New Issue
Block a user