shell-init: record the first command of every shell in atuin

bash-preexec arms its DEBUG trap from __bp_interactive_mode at the end of
PROMPT_COMMAND, but its lazy installer runs mid-way through the first prompt
because zoxide and ghostty append their hooks after it. Those hooks disarm the
trap again, so the first interactive command of every session never reached
preexec and atuin silently dropped it.

Arm from PS0 instead, which is expanded once per interactive command before the
DEBUG trap and is therefore independent of PROMPT_COMMAND ordering.

Also guard the script against running twice: it is installed as both
loginShellInit and interactiveShellInit, and /etc/profile sources /etc/bashrc,
which registered starship's precmd/preexec hooks twice in every login shell.
This commit is contained in:
randogoth 2026-08-28 16:05:43 +03:00
parent 3f29ad6cec
commit 9f28569fef

View file

@ -37,18 +37,43 @@ fi
(( INTERACTIVE )) || return 0
# ---- run-once guard --------------------------------------------------------
# Installed as both loginShellInit (/etc/profile) and interactiveShellInit
# (/etc/bashrc), and /etc/profile sources /etc/bashrc, so a login shell would run
# this twice and register starship's hooks twice. Not exported: child shells must
# initialise on their own.
if [[ -n ${NANUQSAURUS_SHELL_INIT_DONE:-} ]]; then
log_shell_init "already initialised, skipping"
return 0
fi
NANUQSAURUS_SHELL_INIT_DONE=1
# ---- bash-preexec ----------------------------------------------------------
if [[ -n ${BASH_VERSION:-} && -z ${bash_preexec_imported:-} && -r "@BASHPREEXEC@" ]]; then
# shellcheck disable=SC1090
. "@BASHPREEXEC@"
# Do NOT call __bp_install here. bash-preexec must install on the FIRST prompt,
# after starship/atuin/zoxide/ghostty have modified PROMPT_COMMAND, so that
# __bp_interactive_mode stays the LAST PROMPT_COMMAND entry. Installing eagerly
# lets later hooks land after it and silently breaks preexec (atuin recording).
log_shell_init "bash-preexec loaded"
fi
# bash-preexec arms its DEBUG trap from __bp_interactive_mode, which it appends to
# the END of PROMPT_COMMAND -- but its lazy installer runs in the MIDDLE of the
# first prompt, because zoxide (below) and ghostty (~/.bashrc) append their hooks
# after it. Those hooks then disarm the trap again, so the first command of every
# session never reaches preexec and atuin never records it. PS0 is expanded once
# per interactive command, before the DEBUG trap, so arming there is independent of
# PROMPT_COMMAND ordering. Needs the non-forking ${ ...; } funsub (bash >= 5.3);
# older bash keeps the previous behaviour.
if [[ -n ${bash_preexec_imported:-} ]] &&
((BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3))); then
# shellcheck disable=SC2016
__nanuq_bp_arm='${ __bp_preexec_interactive_mode=on; }'
[[ ${PS0-} == *"$__nanuq_bp_arm"* ]] || PS0=$__nanuq_bp_arm${PS0-}
unset __nanuq_bp_arm
log_shell_init "bash-preexec PS0 arming enabled"
fi
# ---- starship --------------------------------------------------------------
if have @STARSHIP@; then
@ -57,8 +82,7 @@ if have @STARSHIP@; then
*/bash) eval "$(@STARSHIP@ init bash)" ;;
*/zsh) eval "$(@STARSHIP@ init zsh)" ;;
esac
# starship's bash init registers starship_precmd/starship_preexec_all with
# bash-preexec directly; no manual PROMPT_COMMAND wiring needed.
# registers starship_precmd/starship_preexec_all with bash-preexec directly
else
log_shell_init "starship skipped"
fi
@ -77,8 +101,7 @@ if have @ATUIN@; then
*/bash) eval "$(@ATUIN@ init bash)" ;;
*/zsh) eval "$(@ATUIN@ init zsh)" ;;
esac
# atuin's init registers __atuin_preexec/__atuin_precmd with bash-preexec
# itself; manual array wiring only caused duplicate hook entries.
# registers __atuin_preexec/__atuin_precmd with bash-preexec itself
else
log_shell_init "atuin skipped"
fi
@ -91,9 +114,7 @@ if have zoxide; then
*/bash) eval "$(zoxide init bash)" ;;
*/zsh) eval "$(zoxide init zsh)" ;;
esac
# zoxide's bash init hooks into bash-preexec (precmd_functions) when it is
# present; manual PROMPT_COMMAND wiring only pushed hooks past
# __bp_interactive_mode and broke preexec ordering.
# appends __zoxide_hook to PROMPT_COMMAND; see the PS0 note above
else
log_shell_init "zoxide skipped"
fi