Bash shell integration: PROMPT_COMMAND array entries from other profile scripts get misreported as the current command
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
`shellIntegration-bash.sh` assumes `PROMPT_COMMAND` is a plain string. On bash 5.1+, other profile scripts can append their own function to `PROMPT_COMMAND` as a **second array element** instead of concatenating a string — e.g. systemd's `/usr/lib/systemd/profile.d/80-systemd-osc-context.sh` does:
```sh
PROMPT_COMMAND+=(__systemd_osc_context_precmdline)
```
`shellIntegration-bash.sh` does `PROMPT_COMMAND=__vsc_prompt_cmd` to install its own hook. Since `PROMPT_COMMAND` is already an array, this bash quirk only overwrites index `[0]`; other scripts' entries at higher indices survive and still get executed by bash as their own top-level commands.
When bash is about to run that surviving array entry, the DEBUG trap fires (`$BASH_COMMAND` = the foreign function's name), and since it doesn't match the `__vsc_prompt*` filter in `__vsc_preexec`, it gets treated as "the command the user just ran." This is reported via OSC 633;E and used by the terminal (e.g. VS Code's own tab title / "current command" indicators, and other embedders like Qt Creator that reuse this script) — so the literal string `__systemd_osc_context_precmdline` shows up as the running-command/title instead of the actual command. Worse: because `__vsc_in_command_execution` also gets consumed by this spurious trigger, the *real* next command's own preexec never fires, so command detection is permanently stuck/broken for the session (reproduced with a plain interactive bash + `VSCODE_SHELL_LOGIN=1`).
**Repro:**
1. Have `/usr/lib/systemd/profile.d/80-systemd-osc-context.sh` installed (systemd 256+, e.g. current Arch/Ubuntu).
2. Launch a login shell with VS Code / Qt Creator shell integration injected (`VSCODE_SHELL_LOGIN=1`, `VSCODE_INJECTION=1`, `bash --init-file shellIntegration-bash.sh -i`).
3. Observe the very first OSC 633;E sequence emitted contains `__systemd_osc_context_precmdline` instead of being empty, and no subsequent real command ever gets its own OSC 633;E report.
**Fix:** don't treat `$BASH_COMMAND` as a real command if it matches any current entry of the (possibly array) `PROMPT_COMMAND`, and don't consume the "idle" execution-state guard (`__vsc_in_command_execution`) for such entries. Patch (verified locally, restores correct command detection for both the buggy and non-buggy paths):
```diff
--- a/shellIntegration-bash.sh
+++ b/shellIntegration-bash.sh
@@ -405,9 +405,28 @@ __vsc_precmd() {
__vsc_update_env
}
+__vsc_is_prompt_command_entry() {
+ # PROMPT_COMMAND may be an array (bash 5.1+) with entries appended by other
+ # profile scripts (e.g. systemd's 80-systemd-osc-context.sh). Bash's DEBUG
+ # trap fires for each array entry as its own top-level command, so those
+ # entries (as well as our own __vsc_prompt_start/__vsc_prompt_end) must not
+ # be mistaken for a user-entered command, and must not consume the
+ # preexec slot that is meant for the next real command.
+ builtin local cmd="$1" __vsc_pc_entry
+ if [[ "$cmd" == __vsc_prompt* ]]; then
+ builtin return 0
+ fi
+ for __vsc_pc_entry in "${PROMPT_COMMAND[@]}"; do
+ if [[ -n "$__vsc_pc_entry" && "$cmd" == "$__vsc_pc_entry" ]]; then
+ builtin return 0
+ fi
+ done
+ builtin return 1
+}
+
__vsc_preexec() {
__vsc_initialized=1
- if [[ ! $BASH_COMMAND == __vsc_prompt* ]]; then
+ if ! __vsc_is_prompt_command_entry "$BASH_COMMAND"; then
# Use history if it's available to verify the command as BASH_COMMAND comes in with aliases
# resolved
if [ "$__vsc_history_verify" = "1" ]; then
@@ -436,7 +455,7 @@ else
if [[ -z "$__vsc_dbg_trap" ]]; then
__vsc_preexec_only() {
- if [ "$__vsc_in_command_execution" = "0" ]; then
+ if [ "$__vsc_in_command_execution" = "0" ] && ! __vsc_is_prompt_command_entry "$BASH_COMMAND"; then
__vsc_in_command_execution="1"
__vsc_preexec
fi
@@ -444,10 +463,12 @@ else
trap '__vsc_preexec_only "$_"' DEBUG
elif [[ "$__vsc_dbg_trap" != '__vsc_preexec "$_"' && "$__vsc_dbg_trap" != '__vsc_preexec_all "$_"' ]]; then
__vsc_preexec_all() {
- if [ "$__vsc_in_command_execution" = "0" ]; then
+ if [ "$__vsc_in_command_execution" = "0" ] && ! __vsc_is_prompt_command_entry "$BASH_COMMAND"; then
__vsc_in_command_execution="1"
__vsc_preexec
builtin eval "${__vsc_dbg_trap}"
+ elif [ "$__vsc_in_command_execution" = "0" ]; then
+ builtin eval "${__vsc_dbg_trap}"
fi
}
trap '__vsc_preexec_all "$_"' DEBUG
```
Contributor guide
Assessment
This issue has not been assessed yet.