[ZSH] Faster startup: instant prompt, single compinit, cached probes

Re-enable p10k instant prompt (sudo-key ssh-add moved above it). Drop
oh-my-zsh.sh (second compinit, update check); set up what OMZ plugins
need ourselves. Cache java_home, go env, fzf --zsh output. Disable the
kubecontext segment (ran kubectl per shell). Log starts over 1 s to
~/.cache/zsh/startup-slow.log to catch cold-start stalls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Fabian IsingandClaude Opus 5.5 committed 2026-10-14 10:53:36 +02:00
1 parent 604c1c6538
commit 74d1d60845
6 files changed
+150 -59

No files matched your search

+3 -1
View File
@@ -1,6 +1,8 @@
export LC_ALL=en_US.UTF-8 export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8 export LANG=en_US.UTF-8
export JAVA_HOME=`/usr/libexec/java_home` # deps: system/user JDK dirs, which change when a JDK is added or removed
_zsh_cached java_home /Library/Java/JavaVirtualMachines ~/Library/Java/JavaVirtualMachines \
-- /usr/libexec/java_home && read -r JAVA_HOME < $REPLY && export JAVA_HOME
# macOS resets this on updates (and at random); background it, it's slow # macOS resets this on updates (and at random); background it, it's slow
defaults write .GlobalPreferences AppleLocale en_DE &! defaults write .GlobalPreferences AppleLocale en_DE &!
+1 -1
View File
@@ -71,7 +71,7 @@
phpenv # php version from phpenv (https://github.com/phpenv/phpenv) phpenv # php version from phpenv (https://github.com/phpenv/phpenv)
scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv) scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv)
haskell_stack # haskell version from stack (https://haskellstack.org/) haskell_stack # haskell version from stack (https://haskellstack.org/)
kubecontext # current kubernetes context (https://kubernetes.io/) # kubecontext # off: runs kubectl on each shell's first prompt, even when hidden
terraform # terraform workspace (https://www.terraform.io) terraform # terraform workspace (https://www.terraform.io)
# terraform_version # terraform version (https://www.terraform.io) # terraform_version # terraform version (https://www.terraform.io)
aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
+1 -2
View File
@@ -1,5 +1,4 @@
# Load OMZ plugins # Load OMZ lib and plugins; not oh-my-zsh.sh itself (own compinit, theme, update check)
ohmyzsh/ohmyzsh
ohmyzsh/ohmyzsh path:lib ohmyzsh/ohmyzsh path:lib
ohmyzsh/ohmyzsh path:plugins/git ohmyzsh/ohmyzsh path:plugins/git
ohmyzsh/ohmyzsh path:plugins/golang ohmyzsh/ohmyzsh path:plugins/golang
+74 -55
View File
@@ -1,47 +1,11 @@
# Powerlevel10k instant prompt and machine-local layers must remain first. source "${ZDOTDIR:-$HOME}/.zsh/lib/startup.zsh"
[[ -f ~/.zsh/.powerline_config ]] && source ~/.zsh/.powerline_config [[ -d ${ZDOTDIR:-$HOME}/.cache/zsh ]] || mkdir -p ${ZDOTDIR:-$HOME}/.cache/zsh
[[ -f ~/.zsh/.user_config ]] && source ~/.zsh/.user_config
[[ -f ~/.zsh/.virtual_env_config.zsh ]] && source ~/.zsh/.virtual_env_config.zsh
[[ -f ~/.zsh/.local_config ]] && source ~/.zsh/.local_config
[[ -f ~/.zsh/.env.local ]] && source ~/.zsh/.env.local
source "${ZDOTDIR:-$HOME}/.zsh/lib/updates.zsh"
export LC_OSC52=1
# Completions for tools installed under ~/.local (e.g. tools-repo's safe*
# wrappers). Must precede antidote, whose completion plugin runs compinit.
fpath=("$HOME/.local/share/zsh/site-functions" $fpath)
plugins_txt=${ZDOTDIR:-$HOME}/.zsh/.zsh_plugins.txt
static_file=${ZDOTDIR:-$HOME}/.cache/zsh/.zsh_plugins.zsh
source "${ZDOTDIR:-$HOME}/.zsh/lib/antidote.zsh"
source "${ZDOTDIR:-$HOME}/.zsh/lib/interactive.zsh"
bindkey '^w' kill-region
bindkey -v
VI_MODE_SET_CURSOR=true
# fzf keys (Ctrl-R/Ctrl-T/Alt-C, **<Tab>); after bindkey -v so vi keymaps get them
(( $+commands[fzf] )) && source <(fzf --zsh)
[[ -f ~/.zsh/.mac_config.zsh ]] && source ~/.zsh/.mac_config.zsh
if (( $+commands[go] )); then
export GOPATH="$(go env GOPATH 2>/dev/null)"
[[ -n "$GOPATH" ]] && path+=("$GOPATH/bin")
fi
# Use safe wrappers after ~/.local/bin has been added to PATH.
# Agent shells: </dev/null makes the prompt decline (exit 1) instead of blocking.
if [[ -n ${CLAUDECODE:-} || -n ${CODEX_SHELL:-} ]]; then
_safe_redir=' </dev/null'
fi
(( $+commands[safecp] )) && alias cp="safecp${_safe_redir:-}"
(( $+commands[safemv] )) && alias mv="safemv${_safe_redir:-}"
(( $+commands[safescp] )) && alias scp="safescp${_safe_redir:-}"
unset _safe_redir
# May ask for the key passphrase (once per boot), so it precedes instant prompt.
_in_coding_agent="$CLAUDECODE$CODEX_SESSION_ID$PI_CODING_AGENT" _in_coding_agent="$CLAUDECODE$CODEX_SESSION_ID$PI_CODING_AGENT"
if [[ -f ~/.ssh/sudo_key && -z $_in_coding_agent ]]; then if [[ -f ~/.ssh/sudo_key && -z $_in_coding_agent ]] &&
_sudo_fp=$(ssh-keygen -lf ~/.ssh/sudo_key | awk '{print $2}') _zsh_cached sudo_key.fp ~/.ssh/sudo_key -- ssh-keygen -lf ~/.ssh/sudo_key; then
read -r _ _sudo_fp _ < $REPLY
[[ -e /tmp/sudo-agent.sock ]] || ssh-agent -a /tmp/sudo-agent.sock &>/dev/null [[ -e /tmp/sudo-agent.sock ]] || ssh-agent -a /tmp/sudo-agent.sock &>/dev/null
SSH_AUTH_SOCK=/tmp/sudo-agent.sock ssh-add -l | grep -q "$_sudo_fp" || SSH_AUTH_SOCK=/tmp/sudo-agent.sock ssh-add -l | grep -q "$_sudo_fp" ||
SSH_AUTH_SOCK=/tmp/sudo-agent.sock ssh-add ~/.ssh/sudo_key SSH_AUTH_SOCK=/tmp/sudo-agent.sock ssh-add ~/.ssh/sudo_key
@@ -54,8 +18,68 @@ if [[ -f ~/.ssh/sudo_key && -z $_in_coding_agent ]]; then
SSH_AUTH_SOCK=/tmp/sudo-agent-confirm.sock ssh-add -c ~/.ssh/sudo_key SSH_AUTH_SOCK=/tmp/sudo-agent-confirm.sock ssh-add -c ~/.ssh/sudo_key
unset _sudo_fp unset _sudo_fp
fi fi
_zst_mark sudo-agent
# Powerlevel10k instant prompt; nothing below may need console input.
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
fi
_zst_mark instant-prompt
# Machine-local layers.
[[ -f ~/.zsh/.powerline_config ]] && source ~/.zsh/.powerline_config
[[ -f ~/.zsh/.user_config ]] && source ~/.zsh/.user_config
[[ -f ~/.zsh/.virtual_env_config.zsh ]] && source ~/.zsh/.virtual_env_config.zsh
[[ -f ~/.zsh/.local_config ]] && source ~/.zsh/.local_config
[[ -f ~/.zsh/.env.local ]] && source ~/.zsh/.env.local
_zst_mark layers
source "${ZDOTDIR:-$HOME}/.zsh/lib/updates.zsh"
export LC_OSC52=1
_zst_mark updates
# Completions for tools installed under ~/.local (e.g. tools-repo's safe*
# wrappers). Must precede antidote, whose completion plugin runs compinit.
fpath=("$HOME/.local/share/zsh/site-functions" $fpath)
plugins_txt=${ZDOTDIR:-$HOME}/.zsh/.zsh_plugins.txt
static_file=${ZDOTDIR:-$HOME}/.cache/zsh/.zsh_plugins.zsh
source "${ZDOTDIR:-$HOME}/.zsh/lib/antidote.zsh"
_zst_mark antidote
source "${ZDOTDIR:-$HOME}/.zsh/lib/interactive.zsh"
bindkey '^w' kill-region
bindkey -v
VI_MODE_SET_CURSOR=true
# fzf keys (Ctrl-R/Ctrl-T/Alt-C, **<Tab>); after bindkey -v so vi keymaps get them
(( $+commands[fzf] )) && _zsh_cached fzf.zsh ${commands[fzf]:A} -- fzf --zsh &&
source $REPLY
_zst_mark fzf
[[ -f ~/.zsh/.mac_config.zsh ]] && source ~/.zsh/.mac_config.zsh
_zst_mark mac-config
# deps: go binary and `go env -w` files (macOS, Linux)
if (( $+commands[go] )) && _zsh_cached gopath ${commands[go]:A} \
"$HOME/Library/Application Support/go/env" "${XDG_CONFIG_HOME:-$HOME/.config}/go/env" \
-- env -u GOPATH go env GOPATH; then
[[ -n $GOPATH ]] || read -r GOPATH < $REPLY
export GOPATH
path+=("$GOPATH/bin")
fi
_zst_mark go
# Use safe wrappers after ~/.local/bin has been added to PATH.
# Agent shells: </dev/null makes the prompt decline (exit 1) instead of blocking.
if [[ -n ${CLAUDECODE:-} || -n ${CODEX_SHELL:-} ]]; then
_safe_redir=' </dev/null'
fi
(( $+commands[safecp] )) && alias cp="safecp${_safe_redir:-}"
(( $+commands[safemv] )) && alias mv="safemv${_safe_redir:-}"
(( $+commands[safescp] )) && alias scp="safescp${_safe_redir:-}"
unset _safe_redir
# no GUI password dialog for sudo from agent shells; a non-interactive sudo just fails # no GUI password dialog for sudo from agent shells; a non-interactive sudo just fails
[[ -z $_in_coding_agent ]] && export SUDO_ASKPASS=$(command -v ssh-askpass) [[ -z $_in_coding_agent ]] && export SUDO_ASKPASS=${commands[ssh-askpass]}
unset _in_coding_agent unset _in_coding_agent
if [[ -f ~/dotfiles/zsh/.p10k.mac.zsh ]]; then if [[ -f ~/dotfiles/zsh/.p10k.mac.zsh ]]; then
@@ -64,6 +88,7 @@ else
p10k_config=~/dotfiles/zsh/.p10k.zsh p10k_config=~/dotfiles/zsh/.p10k.zsh
fi fi
source "${ZDOTDIR:-$HOME}/.zsh/lib/prompt.zsh" source "${ZDOTDIR:-$HOME}/.zsh/lib/prompt.zsh"
_zst_mark prompt
alias screenshot_mode='powerlevel10k_plugin_unload; export PS1="$ "; python3 ~/.config/alacritty/screenshot_mode.py on' alias screenshot_mode='powerlevel10k_plugin_unload; export PS1="$ "; python3 ~/.config/alacritty/screenshot_mode.py on'
alias screenshot_mode_off='prompt_powerlevel9k_setup; python3 ~/.config/alacritty/screenshot_mode.py off' alias screenshot_mode_off='prompt_powerlevel9k_setup; python3 ~/.config/alacritty/screenshot_mode.py off'
@@ -71,18 +96,9 @@ alias screenshot_mode_off='prompt_powerlevel9k_setup; python3 ~/.config/alacritt
[[ -d "$HOME/.lmstudio/bin" ]] && path+=("$HOME/.lmstudio/bin") [[ -d "$HOME/.lmstudio/bin" ]] && path+=("$HOME/.lmstudio/bin")
[[ -d "$HOME/.runai/bin" ]] && path+=("$HOME/.runai/bin") [[ -d "$HOME/.runai/bin" ]] && path+=("$HOME/.runai/bin")
runai_bin="$HOME/.runai/bin/runai" runai_bin="$HOME/.runai/bin/runai"
runai_completion=${ZDOTDIR:-$HOME}/.cache/zsh/runai-completion.zsh [[ -x "$runai_bin" ]] &&
if [[ -x "$runai_bin" ]]; then _zsh_cached runai-completion.zsh "$runai_bin" -- "$runai_bin" --quiet completion zsh &&
if [[ ! -s "$runai_completion" || "$runai_bin" -nt "$runai_completion" ]]; then source $REPLY
runai_completion_tmp="$runai_completion.$$.tmp"
if "$runai_bin" --quiet completion zsh >| "$runai_completion_tmp"; then
command mv -f "$runai_completion_tmp" "$runai_completion"
else
command rm -f "$runai_completion_tmp"
fi
fi
[[ -s "$runai_completion" ]] && source "$runai_completion"
fi
# Keep this last: activate must snapshot the fully built PATH for deactivate. # Keep this last: activate must snapshot the fully built PATH for deactivate.
typeset -U path PATH typeset -U path PATH
@@ -92,3 +108,6 @@ if [[ -n "$VIRTUAL_ENV" && -f "$VIRTUAL_ENV/bin/activate" ]]; then
source "$VIRTUAL_ENV/bin/activate" source "$VIRTUAL_ENV/bin/activate"
fi fi
fi fi
_zst_mark rest
autoload -Uz add-zsh-hook
add-zsh-hook precmd _zst_report
+18
View File
@@ -4,9 +4,27 @@ if [[ ! -e ${ZDOTDIR:-$HOME}/.antidote ]]; then
git clone https://github.com/mattmc3/antidote.git "${ZDOTDIR:-$HOME}/.antidote" git clone https://github.com/mattmc3/antidote.git "${ZDOTDIR:-$HOME}/.antidote"
fi fi
# OMZ lib/plugins without oh-my-zsh.sh, which would set these up
ZSH_CACHE_DIR=${ZDOTDIR:-$HOME}/.cache/zsh/omz
[[ -d $ZSH_CACHE_DIR/completions ]] || mkdir -p "$ZSH_CACHE_DIR/completions"
fpath=("$ZSH_CACHE_DIR/completions" $fpath)
# ssh-agent plugin keys its env file on this; HOST changes with VPN rDNS
SHORT_HOST=${HOST/.*/}
if [[ $OSTYPE == darwin* ]] && _zsh_cached short_host \
/Library/Preferences/SystemConfiguration/preferences.plist -- scutil --get LocalHostName; then
read -r SHORT_HOST < $REPLY
fi
# OMZ calls compdef before compinit (last plugin) has run: queue, replay below
typeset -ga _compdef_queue
compdef() { _compdef_queue+=("${(j: :)${(@q)@}}") }
zstyle ':completion:*:ssh:*' hosts off zstyle ':completion:*:ssh:*' hosts off
zstyle ':completion:*:scp:*' hosts off zstyle ':completion:*:scp:*' hosts off
zstyle ':completion:*:commands' rehash 1 zstyle ':completion:*:commands' rehash 1
source "${ZDOTDIR:-$HOME}/.antidote/antidote.zsh" source "${ZDOTDIR:-$HOME}/.antidote/antidote.zsh"
antidote load "$plugins_txt" "$static_file" antidote load "$plugins_txt" "$static_file"
if (( ${+_comps} )); then
for _c in $_compdef_queue; do eval "compdef $_c"; done
unset _c _compdef_queue
fi
+53
View File
@@ -0,0 +1,53 @@
# Startup helpers: output caching and slow-start logging.
zmodload zsh/datetime
zmodload -F zsh/stat b:zstat
# Cache a command's output; regenerate when a dependency's resolved path or
# mtime changes (brew upgrades swap Cellar paths but keep old mtimes).
# Usage: _zsh_cached <name> [dep...] -- <cmd...>; sets REPLY to the cache file.
_zsh_cached() {
local file=${ZDOTDIR:-$HOME}/.cache/zsh/$1 stamp old
local -a mt
shift
while [[ $# -gt 0 && $1 != -- ]]; do
mt=(); zstat -A mt +mtime -- $1 2>/dev/null
stamp+="${1:A}@${mt[1]:-none};"
shift
done
shift
[[ -r $file.stamp ]] && read -r old < $file.stamp
if [[ ! -s $file || $old != $stamp ]]; then
local tmp=$file.$$.tmp
if "$@" >| $tmp 2>/dev/null && [[ -s $tmp ]]; then
command mv -f $tmp $file
print -r -- $stamp >| $file.stamp
else
command rm -f $tmp
[[ -s $file ]] || return 1
fi
fi
REPLY=$file
}
# Slow-start log: per-step ms, written only if startup (until the first prompt)
# exceeds ZSH_STARTUP_LOG_MS. Diagnostic for cold starts; remove once resolved.
typeset -gF _zst_t0=$EPOCHREALTIME _zst_last=$EPOCHREALTIME
typeset -ga _zst_steps
_zst_mark() {
local ms
printf -v ms '%s=%.0f' $1 $(( (EPOCHREALTIME - _zst_last) * 1000 ))
_zst_steps+=($ms)
_zst_last=$EPOCHREALTIME
}
_zst_report() {
add-zsh-hook -d precmd _zst_report
_zst_mark precmd
local total=$(( (EPOCHREALTIME - _zst_t0) * 1000 ))
(( total >= ${ZSH_STARTUP_LOG_MS:-1000} )) || return 0
local ts login=0
strftime -s ts '%Y-%m-%dT%H:%M:%S' $EPOCHSECONDS
[[ -o login ]] && login=1
printf '%s total=%.0f tmux=%d login=%d | %s\n' $ts $total ${+TMUX} $login "$_zst_steps" \
>> ${ZDOTDIR:-$HOME}/.cache/zsh/startup-slow.log
}