Skip to content

Commit 51ba5a1

Browse files
committed
fix: compose bash bootstrap PROMPT_COMMAND instead of clobbering it
Replace the bootstrap's unconditional unset PROMPT_COMMAND with a self-strip keyed on a trailing marker: remove only Programa's own bootstrap text and keep whatever the user's startup files appended before the first prompt (e.g. starship_precmd from eval "$(starship init bash)"). programa-bash-integration.bash's existing PROMPT_COMMAND merge then prepends _cmux_prompt_command, yielding _cmux_prompt_command;starship_precmd so starship_precmd runs on every prompt instead of being discarded after the first one. Also add a plain-bash no-regression case alongside the fix.
1 parent 64c502f commit 51ba5a1

2 files changed

Lines changed: 50 additions & 1 deletion

File tree

Resources/shell-integration/programa-bash-bootstrap.bash

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,24 @@
66
# interactive prompt: it sources Programa's bash integration and then hands
77
# control to _programa_prompt_command.
88
#
9+
# COMPOSE, don't clobber. A user's startup files may have appended their own
10+
# command to PROMPT_COMMAND *after* this bootstrap before the first prompt --
11+
# most notably `eval "$(starship init bash)"`, which appends starship_precmd.
12+
# We must remove only this bootstrap and keep whatever the user appended, so
13+
# hooks like starship_precmd keep running on every prompt instead of being
14+
# wiped after the first one.
15+
#
16+
# How: strip everything up to and including the marker at the end of this
17+
# script (the marker is the last occurrence, so the greedy ## match lands on
18+
# it), which leaves exactly the user's appended tail. Then trim the leading
19+
# separator and let programa-bash-integration.bash's PROMPT_COMMAND merge
20+
# prepend _cmux_prompt_command.
21+
#
922
# This file is the single source of truth. Sources/GhosttyTerminalView.swift
1023
# reads it (stripping these comments) and exports it as PROMPT_COMMAND, and
1124
# programaTests/GhosttyConfigTests.swift exercises it.
12-
unset PROMPT_COMMAND
25+
PROMPT_COMMAND="${PROMPT_COMMAND##*__programa_bash_bootstrap_marker__}"
26+
while [[ "$PROMPT_COMMAND" == [[:space:]\;]* ]]; do PROMPT_COMMAND="${PROMPT_COMMAND#?}"; done
1327
if [[ "${PROGRAMA_LOAD_GHOSTTY_BASH_INTEGRATION:-0}" == "1" && -n "${GHOSTTY_RESOURCES_DIR:-}" ]]; then
1428
_programa_ghostty_bash="$GHOSTTY_RESOURCES_DIR/shell-integration/bash/ghostty.bash"
1529
[[ -r "$_programa_ghostty_bash" ]] && source "$_programa_ghostty_bash"
@@ -20,3 +34,4 @@ if [[ "${PROGRAMA_SHELL_INTEGRATION:-1}" != "0" && -n "${PROGRAMA_SHELL_INTEGRAT
2034
fi
2135
unset _programa_ghostty_bash _programa_bash_integration
2236
if declare -F _programa_prompt_command >/dev/null 2>&1; then _programa_prompt_command; fi
37+
: __programa_bash_bootstrap_marker__

programaTests/GhosttyConfigTests.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3374,6 +3374,10 @@ final class ZshShellIntegrationHandoffTests: XCTestCase {
33743374
pc.contains("_cmux_prompt_command"),
33753375
"Programa's own prompt hook is missing from PROMPT_COMMAND at prompt \(i): <\(pc)>" + debug
33763376
)
3377+
XCTAssertFalse(
3378+
pc.contains("__programa_bash_bootstrap_marker__"),
3379+
"bootstrap marker leaked into PROMPT_COMMAND at prompt \(i): <\(pc)>" + debug
3380+
)
33773381
}
33783382

33793383
let ps1Values = (1...3).map { fields["PS1_\($0)"] ?? "" }
@@ -3395,6 +3399,36 @@ final class ZshShellIntegrationHandoffTests: XCTestCase {
33953399
)
33963400
}
33973401

3402+
func testBashBootstrapInstallsPromptCommandWithoutUserHook() throws {
3403+
// No-regression guard: with no user PROMPT_COMMAND hook (plain bash,
3404+
// no Starship or similar prompt framework), the bootstrap must still
3405+
// install Programa's own prompt hook -- and must not leak its
3406+
// internal marker into PROMPT_COMMAND.
3407+
let fields = try runBashBootstrapDriver(withUserPromptHook: false)
3408+
let debug = "\n\nstdout:\n\(fields["__stdout__"] ?? "")\nstderr:\n\(fields["__stderr__"] ?? "")"
3409+
3410+
for i in 1...3 {
3411+
let pc = fields["PC_\(i)"] ?? ""
3412+
// Assert the observable contract (hook installed, bootstrap
3413+
// marker removed, no phantom hook) rather than the exact
3414+
// integration-internal string, so this stays green if
3415+
// programa-bash-integration.bash ever tweaks its PROMPT_COMMAND
3416+
// merge.
3417+
XCTAssertTrue(
3418+
pc.contains("_cmux_prompt_command"),
3419+
"plain-bash bootstrap did not install Programa's prompt hook at prompt \(i): <\(pc)>" + debug
3420+
)
3421+
XCTAssertFalse(
3422+
pc.contains("__programa_bash_bootstrap_marker__"),
3423+
"bootstrap marker leaked into PROMPT_COMMAND at prompt \(i): <\(pc)>" + debug
3424+
)
3425+
XCTAssertFalse(
3426+
pc.contains("starship_precmd"),
3427+
"unexpected starship hook in plain-bash PROMPT_COMMAND at prompt \(i): <\(pc)>" + debug
3428+
)
3429+
}
3430+
}
3431+
33983432
/// Drives `Resources/shell-integration/programa-bash-bootstrap.bash`
33993433
/// through real bash, modeling bash's "evaluate PROMPT_COMMAND before
34003434
/// each prompt" loop directly (deterministic, no PTY needed). When

0 commit comments

Comments
 (0)