| name | koopa-atuin |
| description | atuin configuration, bash hook architecture, installer patterns, and DB maintenance for koopa. Use when debugging atuin history recording, editing activation functions, working on the bash-preexec dependency, or resetting the atuin database.
|
koopa atuin
Bash Hook Architecture
atuin requires an explicit preexec backend in bash — unlike zsh (add-zsh-hook) and
fish (--on-event fish_preexec), bash has no native preexec/precmd mechanism.
koopa uses bash-preexec as the backend via a dedicated
_koopa_activate_bash_preexec() function:
$KOOPA_PREFIX/opt/bash-preexec/share/bash-preexec/bash-preexec.sh
Activation ordering is load-bearing. bash-preexec must be sourced before starship.
starship's bash init has a framework-detection branch — if bash_preexec_imported is
not set when starship inits, it falls back to a raw DEBUG trap + PROMPT_COMMAND instead
of appending to preexec_functions[]. When bash-preexec then loads afterward, the hooks
are not cleanly chained and __atuin_preexec never fires.
Order in _koopa_activate_bash_extras:
_koopa_activate_bash_preexec # MUST be first (before prompt + reverse-search)
_koopa_activate_bash_prompt # starship — detects bash_preexec_imported, uses elif branch
_koopa_activate_bash_reverse_search # atuin
Verification
ATUIN_PREEXEC_BACKEND is updated inside __atuin_preexec, so echoing it always
shows the value from before that command ran — it will always print 1:none for the
echo itself. Use the hook arrays and DEBUG trap instead:
trap -p DEBUG
printf '%s\n' "${preexec_functions[@]}"
atuin history list | tail -5
Why not ble.sh
ble.sh (the other supported backend) is more accurate for edge cases (subshells,
broken commands, timing) but is a full readline replacement — it changes keybinding
dispatch and line editing across the entire session. That conflicts with other tools
koopa wires into readline (fzf, atuin's own bind -x). bash-preexec does one thing:
provide hooks. Its narrow scope is the right fit for a tool-composing bootloader.
Activation Files
Never edit lang/bash/include/functions.sh directly — it is generated by
koopa develop cache-functions (concatenates lang/bash/functions/**/*.sh). Always
edit the standalone functions/ sources, then regenerate.
Relevant standalone files:
| File | Purpose |
|---|
lang/bash/functions/activate/activate-bash-preexec.sh | sources bash-preexec.sh (must run before starship) |
lang/bash/functions/activate/activate-bash-extras.sh | ordering: preexec → prompt → reverse-search |
lang/bash/functions/activate/activate-atuin.sh | caches + sources atuin bash init only |
lang/zsh/functions/activate/activate-atuin.sh | zsh/bash via $KOOPA_SHELL; native hooks, no bash-preexec |
lang/fish/functions/activate/activate-atuin.fish | fish; native --on-event hooks |
zsh uses add-zsh-hook preexec/precmd (native). Fish uses --on-event fish_preexec/fish_postexec (native). Neither needs bash-preexec.
The atuin init output is cached at:
$XDG_CACHE_HOME/koopa/shell-init/atuin-{bash,zsh}.sh
$XDG_CACHE_HOME/koopa/shell-init/atuin-fish.fish
Cache is invalidated when the atuin binary is newer than the cache file.
app.json / Installer
- atuin has
"soft_dependencies": ["bash-preexec"] — installs bash-preexec
automatically alongside atuin without making it a hard dependency.
- bash-preexec installer:
lang/python/src/koopa/installers/bash_preexec.py
— registered as "bash-preexec" in installers/__init__.py PYTHON_INSTALLERS.
- bash-preexec is a plain
.sh file, not an archive. The installer must use
download(), not download_with_mirror(). download_with_mirror validates archive
magic bytes and will silently reject a .sh file, causing a spurious 404 fallback
chain even when the download succeeds.
DB Maintenance
Stale / corrupted entries
A past atuin import zsh with an old atuin version can import zsh EXTENDED_HISTORY
lines literally (e.g. ": 1780347995:0;ll") instead of parsing the real command.
These appear in atuin stats as the bare : command dominating the top.
Detect:
atuin history list | awk -F'\t' '{print $2}' | grep "^: [0-9]" | wc -l
Fix — reset and re-import:
cp ~/.local/share/atuin/history.db ~/.local/share/atuin/history.db.bak
rm ~/.local/share/atuin/history.db \
~/.local/share/atuin/history.db-shm \
~/.local/share/atuin/history.db-wal
atuin import bash
atuin import zsh
Always use explicit shell names on macOS — atuin import auto reads $SHELL which
is /bin/zsh (system default) regardless of what shell is actually running, so it
silently imports the wrong history. See koopa-app-registry skill.
HISTSIZE cap
koopa sets HISTSIZE=1000 / SAVEHIST=1000 if unset. This caps ~/.bash_history
and ~/.zsh_history at ~1000 entries, so a re-import seeds at most ~1000 commands.
Live capture via the preexec hook is unaffected by this limit.
config.toml
Chezmoi source: opt/dotfiles/chezmoi/dot_config/atuin/config.toml.tmpl
Deployed: ~/.config/atuin/config.toml
Active settings (2026-06):
search_mode = "fuzzy"
filter_mode = "global"
style = "compact"
inline_height = 40
show_preview = true
show_help = false
invert = true
[theme]
name = "dracula-pro"
filter_mode_shell_up_key_binding, history_filter, store_failed, and all sync
settings are absent (atuin defaults apply). No ATUIN_* env vars are set by koopa.