Zsh History Actor Tagging¶
Automatically tag every command written to ~/.zsh_history with the actor that executed it — franz, claude, or codex.
Tags are written as shell comments (#@actor), so replaying history remains safe.
How It Works¶
The actor is detected using environment inspection:
CLAUDECODE unset → franz
CLAUDECODE=1, grandparent is codex → codex
CLAUDECODE=1, otherwise → claude
Claude Code sets CLAUDECODE=1 in subprocesses. Codex CLI does the same — a two-level process tree walk disambiguates them.
Zsh's zshaddhistory hook intercepts commands before they're written, appends #@actor, then writes manually via print -sr and suppresses the default write.
Example history entries:
: 1745628000:0;git status #@franz
: 1745628042:0;ls -la /tmp #@claude
: 1745628103:0;npm run build #@codex
Setup¶
Add to ~/.zshrc:
~/.zshrc
_detect_command_actor() {
if [[ -n "$CLAUDECODE" ]]; then
local grandparent=$(ps -o comm= -p $(ps -o ppid= -p $(ps -o ppid= -p $$)) 2>/dev/null | xargs)
if [[ "$grandparent" == *codex* ]]; then
echo "codex"
else
echo "claude"
fi
else
echo "franz"
fi
}
zshaddhistory() {
local cmd="${1%%$'\n'}"
local actor=$(_detect_command_actor)
print -sr -- "${cmd} #@${actor}"
return 1
}
hh() {
local n=${1:-100}
fc -li -${n} | sed -E 's/ #@(claude|codex|franz)/ [\1]/'
}
Ensure these history settings are present:
HISTFILE="$HOME/.zsh_history"
HISTSIZE=100000
SAVEHIST=100000
setopt EXTENDED_HISTORY
setopt INC_APPEND_HISTORY
setopt SHARE_HISTORY
setopt HIST_IGNORE_SPACE
Warning
EXTENDED_HISTORY must be enabled or timestamps will break.
Usage¶
hh # last 100 commands
hh 500 # last 500 commands
hh 1000 | grep '\[claude\]'
hh 1000 | grep '\[codex\]'
hh 1000 | grep '\[franz\]'
Sample output:
891 2026-04-26 02:18 cd ~/projects/api [franz]
892 2026-04-26 02:19 git status [claude]
893 2026-04-26 02:19 npm install [claude]
894 2026-04-26 02:21 git diff HEAD [franz]
895 2026-04-26 02:22 codex exec "refactor auth.ts" [codex]
Note
Existing history is not retroactively tagged.
Limitations¶
- If Claude stops setting
CLAUDECODE, all commands default tofranz - If Codex runs through a wrapper, detection may misclassify as
claude - Deep process trees may require adjusting traversal depth