Commit 3676e24
fix(bling): rearm bash-preexec DEBUG trap on each prompt (projectbluefin#943)
## Root cause — confirmed against bash-preexec 0.6.0 source
Verified by reading the tagged 0.6.0 source directly, not from memory:
- L74: the installer is deferred by appending a string to
`PROMPT_COMMAND`:
`__bp_trap_string="$(trap -p DEBUG)"; trap - DEBUG; __bp_install`
- L289-293: `__bp_install()` **returns early** if
`"${PROMPT_COMMAND[*]}"` already contains `__bp_precmd_invoke_cmd`
- The self-removal code reads
`existing_prompt_command="${PROMPT_COMMAND:-}"` and later assigns
`PROMPT_COMMAND='__bp_precmd_invoke_cmd'`
On Fedora, `PROMPT_COMMAND` is an **array** (bash ≥ 5.1).
`${PROMPT_COMMAND}` and bare assignment address **element `[0]` only**.
So once any other hook (mise, direnv, starship, vte, systemd, zoxide)
pushes the installer out of element `[0]`, it is **never removed** — and
runs on every prompt. Its first act is `trap - DEBUG`; `__bp_install`
then returns early. From prompt 2 onward the DEBUG trap is permanently
empty.
That is exactly the reporter's `declare -p` output:
`__bp_precmd_invoke_cmd` in `[0]`, installer stranded in `[2]`, atuin's
hook absent.
Reproduced empirically in a real prompt loop: `cycle1 trap: [trap --
'__bp_preexec_invoke_exec "$_"' DEBUG]`, `cycle2: []`, `cycle3: []`.
With this fix, all three cycles retain the trap.
Matches upstream rcaloras/bash-preexec#188 (fixed upstream but
insufficient alone) and #186 (still open).
## Non-obvious discovery
Bash does not inherit the DEBUG trap into functions without `functrace`:
`trap -p DEBUG` **inside a function always returns empty**, and `trap -
DEBUG` inside a function is a no-op — but `trap ... DEBUG` inside a
function *does* set it globally.
Two consequences: a detector-guard is impossible (hence the
unconditional re-arm), and it invalidated the first simulation, which
passed **vacuously**. Written up in `docs/skills/shell-scripts.md`.
## Changes
- New `system_files/shared/.../bling/bash-preexec-rearm.sh` — re-arms
the exact trap bash-preexec installs, once per prompt
- `bling.sh` sources it **last**, under the bash guard, so the hook is
the final `PROMPT_COMMAND` entry
`bling.sh` stays POSIX (it is under an sh-dialect shellcheck gate); the
bash-only logic lives in the sibling file. `BLING_DIR` allows bats to
point at the repo tree.
No vendored copy of bash-preexec.
## Tests — 14 cases, all pass
Tests **1 and 4 are negative controls** that pin the bash-preexec
stand-in to the real defect, so the positive cases cannot pass
vacuously.
Covered: array `PROMPT_COMMAND` trap survival, atuin-style hook still
firing, idempotency on double-source, scalar `PROMPT_COMMAND` (no
regression), bash-preexec absent, readonly `PROMPT_COMMAND`, missing
helper, zsh source-safety.
Also green: `bash -n` / `sh -n`, both CI shellcheck invocations, `just
check`, `pre-commit run --all-files`.
## Confidence and limits
High that the DEBUG trap now survives — the failure and its repair are
both directly reproduced. Two things only real hardware can prove:
1. atuin actually writing records end-to-end (tests use a preexec stub,
not atuin 18.18.1)
2. that no downstream hook on a real session appends its own `trap -
DEBUG` **after** our entry
Ordering is safe for everything bling itself initializes.
## Reviewer note
The re-install is **unconditional** per prompt, forced by the functrace
scoping above; it matches the reporter's validated workaround. A third
party's own raw DEBUG trap set after bling would be overwritten each
prompt — but such a trap already breaks bash-preexec today.
## Blast radius — highest in this batch
`bling.sh` is sourced by **every interactive bash shell** on bluefin,
bluefin-lts, and dakota. Recommend lab validation before merge.
Closes projectbluefin#869
---
## Branch state
Updated onto `main` after projectbluefin#926 landed (via `gh pr update-branch`). No
conflict —
this PR's `docs/TESTING.md` row and `Justfile` entry merged cleanly.
## Overlap with the sibling PRs in this batch
**projectbluefin#942** also edits `docs/skills/shell-scripts.md`, but adds different
sections
(this PR: `Bash DEBUG traps are invisible inside functions`, `POSIX-sh
files
cannot hold bash array code`; projectbluefin#942: `image-info.json is build-time
state`).
Verified non-conflicting with a real sequential merge.
No file overlap with projectbluefin#941.
## Pre-merge checklist for the reviewer
- [ ] Lab-validate on real hardware — the tests use a preexec stub, not
atuin
18.18.1, so end-to-end recording is unproven
- [ ] Confirm the unconditional per-prompt trap re-install is acceptable
(see reviewer note above; a detector-guard is impossible due to bash's
functrace scoping)
Co-authored-by: Jorge Castro <jorge@projectbluefin.io>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>1 parent 2281f4f commit 3676e24
7 files changed
Lines changed: 419 additions & 0 deletions
File tree
- docs
- skills/shell-scripts/references
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
| 92 | + | |
92 | 93 | | |
93 | 94 | | |
94 | 95 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
103 | 103 | | |
104 | 104 | | |
105 | 105 | | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
Lines changed: 9 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
Lines changed: 48 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
0 commit comments