Skip to content

History Sync

Aleem Isiaka edited this page Apr 2, 2026 · 1 revision

History Sync

Sync your shell history across machines — encrypted, searchable, and version-controlled in your dotfiles repo.

Table of Contents

Overview

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

How It Works

  1. 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.

  2. Syncing: heimdal history sync (also called by heimdal 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.

  3. 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.

  4. Searching: heimdal history search queries the local cache with substring match or opens an interactive fuzzy picker.

Prerequisites

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 import

See Encryption Key Management for full setup instructions.

Shell Integration

Add the shell hook to your shell's RC file. Run the appropriate init command and follow the instructions:

Zsh

# Add to ~/.zshrc
eval "$(heimdal history shell-init --shell zsh)"

Or append manually:

heimdal history shell-init --shell zsh >> ~/.zshrc
source ~/.zshrc

Bash

# Add to ~/.bashrc
eval "$(heimdal history shell-init --shell bash)"

Fish

# Add to ~/.config/fish/config.fish
heimdal history shell-init --shell fish | source

What the Hook Does

The 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.

Session IDs

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.

Commands

heimdal history sync

Encrypt staging entries and push history to the dotfiles repo.

heimdal history sync

This is called automatically by heimdal sync. Run it manually to flush and encrypt history without a full sync.

heimdal history search

Search history across all machines.

# Substring filter (case-insensitive)
heimdal history search "docker run"

# Interactive fuzzy picker (uses fzf-style selection)
heimdal history search --interactive

Results show: timestamp, hostname, working directory, and command.

heimdal history shell-init

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 fish

heimdal history rekey

Re-encrypt all history files with a new bifrost key. Use this when rotating your key.

heimdal history rekey

After rekey completes, back up the new key:

heimdal key export

See Encryption Key Management — Rekeying for details.

heimdal history record

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"

heimdal history session-id

Print a stable UUID for this shell instance.

heimdal history session-id
# 7b2c4f8e-1a3d-4e9f-b0c2-1234567890ab

Cross-Machine Search

After 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 apply

Configuration

History 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.

Security Model

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.

Troubleshooting

History not recording

  1. Check that the shell hook is installed:
    heimdal history shell-init --shell zsh  # review the output
  2. Source your RC file: source ~/.zshrc
  3. Run a command and check the staging file:
    ls -la ~/.heimdal/history_staging.jsonl

"Bifrost key not configured — skipping history sync"

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 machine

History from other machines not appearing

Run 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 history entries

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.

Cache appears stale

Force a cache rebuild:

heimdal history sync

Next Steps

Clone this wiki locally