-
Notifications
You must be signed in to change notification settings - Fork 0
History Sync
Sync your shell history across machines — encrypted, searchable, and version-controlled in your dotfiles repo.
- Overview
- How It Works
- Prerequisites
- Shell Integration
- Commands
- Cross-Machine Search
- Configuration
- Security Model
- Troubleshooting
Heimdal records every shell command on each machine, encrypts it, and stores it in your dotfiles repo. On any machine running heimdal sync, history from all machines is decrypted and merged into a local cache you can search interactively.
Machine A Machine B
shell hook → staging file shell hook → staging file
│ │
└── heimdal history sync ──git──> dotfiles repo <── heimdal history sync
│
heimdal sync / history sync
│
decrypts + merges
│
~/.heimdal/history.cache
│
heimdal history search
-
Recording: A shell hook appends every command to
~/.heimdal/history_staging.jsonl(plaintext, local only). The hook runs in the background so it never adds latency to your prompt. -
Syncing:
heimdal history sync(also called byheimdal sync) renames the staging file atomically, encrypts each entry with your bifrost-derived history key, and appends them to a per-machine file in your dotfiles repo:history/<hostname>-<machine_id>.jsonl.enc. -
Rebuilding cache: After syncing, Heimdal decrypts all per-machine files, merges and deduplicates entries, sorts by timestamp, and writes a plaintext cache at
~/.heimdal/history.cache. This makes search fast — no per-query decryption. -
Searching:
heimdal history searchqueries the local cache with substring match or opens an interactive fuzzy picker.
A bifrost key is required. Without one, heimdal history sync skips silently.
# Generate a key (first machine)
heimdal key gen
heimdal key export # back it up
# Import on other machines
heimdal key importSee Encryption Key Management for full setup instructions.
Add the shell hook to your shell's RC file. Run the appropriate init command and follow the instructions:
# Add to ~/.zshrc
eval "$(heimdal history shell-init --shell zsh)"Or append manually:
heimdal history shell-init --shell zsh >> ~/.zshrc
source ~/.zshrc# Add to ~/.bashrc
eval "$(heimdal history shell-init --shell bash)"# Add to ~/.config/fish/config.fish
heimdal history shell-init --shell fish | sourceThe hook records every command with:
- Timestamp
- Command text
- Exit code
- Working directory
- Hostname
- Session ID
Commands starting with a space are not recorded (respects HIST_IGNORE_SPACE in zsh and a leading-space convention in bash/fish). The hook runs in the background with all output discarded, so it never interrupts your shell session even if Heimdal is unavailable.
Each shell session gets a stable session ID used to group commands during search. Generate a session ID explicitly:
export HEIMDAL_SESSION=$(heimdal history session-id)The shell hook scripts do this automatically.
Encrypt staging entries and push history to the dotfiles repo.
heimdal history syncThis is called automatically by heimdal sync. Run it manually to flush and encrypt history without a full sync.
Search history across all machines.
# Substring filter (case-insensitive)
heimdal history search "docker run"
# Interactive fuzzy picker (uses fzf-style selection)
heimdal history search --interactiveResults show: timestamp, hostname, working directory, and command.
Print the shell integration code to add to your RC file.
heimdal history shell-init --shell zsh
heimdal history shell-init --shell bash
heimdal history shell-init --shell fishRe-encrypt all history files with a new bifrost key. Use this when rotating your key.
heimdal history rekeyAfter rekey completes, back up the new key:
heimdal key exportSee Encryption Key Management — Rekeying for details.
Record a command from the shell hook. Called automatically by the hook — not intended for manual use.
heimdal history record --cmd "git push" --exit 0 --dir "/home/user/proj" --session "abc123"Print a stable UUID for this shell instance.
heimdal history session-id
# 7b2c4f8e-1a3d-4e9f-b0c2-1234567890abAfter running heimdal sync on a machine, the history cache contains entries from all machines that have synced to the same dotfiles repo.
# Pull history from all machines and rebuild cache
heimdal sync
# Now search across all machines
heimdal history search "terraform apply"
# 2026-03-15 14:32 work-laptop ~/infra terraform apply -auto-approve
# 2026-03-10 09:11 home-desktop ~/infra terraform applyHistory sync respects the history section in heimdal.yaml:
heimdal:
version: "1"
history:
enabled: true # enable history recording (default: true)
sync: true # include history in `heimdal sync` (default: true)Set sync: false to record history locally without pushing it to the dotfiles repo. Set enabled: false to disable history entirely on a machine.
Encrypted in the repo:
-
history/<hostname>-<machine_id>.jsonl.enc— each line is one base64url-encoded XChaCha20-Poly1305 ciphertext
Plaintext on the local machine:
-
~/.heimdal/history_staging.jsonl— commands recorded since last sync -
~/.heimdal/history.cache— merged, searchable cache; rebuilt on every sync
The staging file and cache are intentionally plaintext. Encrypting on every keystroke would add latency to every shell command, and rebuilding the cache requires decrypting all history files on every search query. The threat model assumes the local machine is trusted; the encryption protects history in the git repo, which may be public. If you require encryption at rest on the local machine, use full-disk encryption (FileVault, LUKS, BitLocker).
The cache file is listed in .gitignore automatically by heimdal history sync so it is never committed.
Concurrent syncs: If two heimdal sync processes run at the same time (e.g., a background autosync races a manual sync), the second process exits cleanly with nothing to flush — the first process already atomically renamed the staging file.
- Check that the shell hook is installed:
heimdal history shell-init --shell zsh # review the output
- Source your RC file:
source ~/.zshrc - Run a command and check the staging file:
ls -la ~/.heimdal/history_staging.jsonl
A bifrost key is required to encrypt and sync history. Set one up:
heimdal key gen # new key on first machine
heimdal key import # or import from another machineRun heimdal sync (not just heimdal history sync) to pull from the remote and rebuild the cache. The other machine must have run heimdal history sync and pushed to the shared dotfiles repo.
Corrupt or unreadable entries in a .jsonl.enc file are skipped with a warning during cache rebuild. They do not abort the sync. If many entries are corrupt, it may indicate a key mismatch — verify you are using the same bifrost key on all machines.
Force a cache rebuild:
heimdal history sync- Encryption Key Management - Set up and manage the bifrost key
-
Git Sync - How
heimdal syncworks - Commands Overview - Full command reference