How Suvadu Records Shell History with Under 2ms Overhead
A technical deep-dive into how Suvadu captures every shell command in under 2 milliseconds using Rust, SQLite WAL mode, and native Zsh hooks.
When you're building a tool that hooks into every single command execution, performance isn't a feature. It's a hard requirement. If your shell feels even slightly slower, you've failed. Suvadu captures every command with full metadata in under 2 milliseconds. Here's exactly how.
The Architecture
Suvadu's recording pipeline has three stages, all of which happen between the time you press Enter and the time your next prompt appears:
- Preexec hook (before command runs): Capture the command string and record start time
- Command executes: Suvadu is not involved here at all
- Precmd hook (before next prompt): Capture exit code, compute duration, detect executor, write to SQLite
The overhead you feel is only in steps 1 and 3. Step 1 is essentially free (under 0.1ms). Step 3 is where the real work happens, and it consistently completes in 1.5-2ms.
Stage 1: The Preexec Hook
Zsh provides a preexec function that fires just before any command runs. Here's what Suvadu's hook does:
_suvadu_preexec() {
_SUVADU_CMD="$1"
_SUVADU_START_TIME=$(( ${EPOCHREALTIME%.*} * 1000 + ${EPOCHREALTIME#*.}[:3] ))
} Two variable assignments. That's it.
The key insight is using Zsh's native $EPOCHREALTIME variable, available since Zsh 5.1 (2015). It gives you the current Unix timestamp with microsecond precision as a float (e.g., 1707500000.123456). We extract millisecond precision using pure Zsh string manipulation and arithmetic — no subprocesses at all.
Stage 2: Executor Detection
Before writing to the database, Suvadu needs to know who ran the command. The detection logic is a cascade of environment variable checks:
# Simplified from actual hook code
detect_executor() {
# 1. CI/CD environments
if [[ -n "$CI" ]]; then
# Check GITHUB_ACTIONS, GITLAB_CI, CIRCLECI...
# 2. AI agents
elif [[ -n "$CLAUDE_CODE" ]]; then
executor_type="agent"; executor="claude-code"
elif [[ -n "$CODEX_CLI" ]]; then
executor_type="agent"; executor="codex-cli"
# 3. IDE terminals
elif [[ -n "$CURSOR_INJECTION" ]]; then
executor_type="ide"; executor="cursor"
elif [[ -n "$VSCODE_INJECTION" ]]; then
executor_type="ide"; executor="vscode"
# 4. Human (TTY check)
elif [[ -t 0 ]]; then
executor_type="human"; executor="terminal"
# 5. Fallback
else
executor_type="programmatic"; executor="subprocess"
fi
} Environment variable checks in Zsh are essentially free (nanosecond-scale lookups against the process environment block). The [[ -t 0 ]] TTY check is a single isatty() syscall. The entire detection cascade completes in under 0.5ms.
This detection happens per-command because the executor context can change during a session.
Stage 3: The SQLite Write
The precmd hook calls suv add with all the captured metadata:
suv add \
--session-id "$SUVADU_SESSION_ID" \
--command "$_SUVADU_CMD" \
--cwd "$PWD" \
--exit-code "$exit_code" \
--started-at "$_SUVADU_START_TIME" \
--ended-at "$end_time" \
--executor-type "$executor_type" \
--executor "$executor" This is the most expensive operation in the pipeline. It involves:
- Spawning the
suvbinary (~0.5ms on a warm filesystem cache) - Opening the SQLite database connection (~0.2ms)
- Executing a single parameterized INSERT (~0.5ms)
- Process exit and cleanup (~0.3ms)
Why WAL Mode Matters
SQLite's default journal mode uses rollback journaling, which requires an exclusive lock on the entire database for every write.
WAL (Write-Ahead Logging) mode changes this fundamentally:
// From db.rs - connection initialization
conn.pragma_update(None, "journal_mode", "WAL")?;
conn.pragma_update(None, "synchronous", "NORMAL")?; With WAL mode, writes go to a separate .db-wal file while readers continue accessing the main database file. There's no lock contention.
The synchronous=NORMAL pragma is equally important. The default FULL mode calls fsync() after every transaction (5-20ms). NORMAL only syncs at checkpoint boundaries.
The Insert Query
INSERT INTO entries (
session_id, command, cwd, exit_code,
started_at, ended_at, duration_ms,
context, tag_id, executor_type, executor
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11) The Database Schema
CREATE TABLE entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
command TEXT NOT NULL,
cwd TEXT NOT NULL,
exit_code INTEGER,
started_at INTEGER NOT NULL,
ended_at INTEGER NOT NULL,
duration_ms INTEGER NOT NULL,
context TEXT,
tag_id INTEGER REFERENCES tags(id),
executor_type TEXT,
executor TEXT
);
CREATE INDEX idx_entries_started_at ON entries(started_at);
CREATE INDEX idx_entries_command ON entries(command);
CREATE INDEX idx_entries_session_id ON entries(session_id);
CREATE INDEX idx_entries_tag_id ON entries(tag_id); The started_at index is the most critical. Since the TUI always sorts by recency (ORDER BY e.started_at DESC), this index means the database can walk the B-tree in reverse order without scanning the table.
A B-tree index on command cannot accelerate LIKE '%query%' searches because the leading wildcard prevents prefix matching. However, the index is still valuable for prefix-based queries like arrow key navigation (LIKE 'git comm%'), and SQLite's query planner may use it for covering index scans on simpler queries.
Synchronous by Design
The suv add call is synchronous. It blocks the shell until the write completes. If async, pressing up arrow immediately after a command would sometimes miss the most recent entry. The 1.5ms of synchronous blocking is worth the guarantee.
The Search Side
Search needs to feel instant. Suvadu's TUI uses ratatui and renders on every keystroke:
- User types in the search box
- A parameterized SQL query fires with
LIKE '%input%'plus any active filters - Results are paginated with
LIMIT/OFFSET - The TUI renders the result table, detail pane, and status bar
With 100,000 entries, a filtered search query returns in 2-5ms. With 500,000 entries, still under 10ms.
Arrow Key Frecency
SELECT command FROM entries
WHERE command LIKE 'git comm%'
AND cwd = '/Users/me/project'
ORDER BY started_at DESC
LIMIT 1 OFFSET 3 The prefix match (LIKE 'query%' without a leading wildcard) can use the B-tree index on command.
Putting It All Together
| Stage | Operation | Time |
|---|---|---|
| Preexec | Capture command + $EPOCHREALTIME | <0.1ms |
| Detection | Environment variable cascade | ~0.3ms |
| Spawn | Launch suv binary | ~0.5ms |
| DB Open | SQLite connection + WAL | ~0.2ms |
| Insert | Parameterized INSERT | ~0.5ms |
| Cleanup | Process exit | ~0.3ms |
| Total | ~1.9ms |
Under 2 milliseconds. You won't feel it.
The best developer tools are the ones you forget are running. Suvadu is designed to be invisible until you need it.
Want to try it? Install Suvadu in 30 seconds and start building a better command history.