clash
The core CLI binary and library. See the project README for usage and documentation.
Status Line internals
The clash statusline command powers the Claude Code status bar integration. It uses a stats sidecar pattern to keep rendering fast regardless of session length.
Architecture
PreToolUse hook
→ policy evaluation
→ log_decision() writes audit.jsonl
→ update_session_stats() writes stats.json (atomic: write tmp + rename)
Claude Code (after each assistant message)
→ clash statusline render
→ reads stats.json from /tmp/clash-<session_id>/
→ prints ANSI-colored line to stdout (<10ms)
Stats sidecar (stats.json)
Instead of parsing the growing audit.jsonl on every render, the PreToolUse hook maintains a small JSON file with pre-aggregated counters:
The file is written atomically (write to .stats.json.tmp, then fs::rename) to prevent partial reads by the concurrent render process.
Double-count prevention
Stats are updated in the PreToolUse handler only (cmd/hooks.rs), not inside log_decision(). This is intentional: "ask" decisions trigger both PreToolUse and PermissionRequest hooks, and both re-evaluate the policy via check_permission() → log_decision(). Counting in log_decision would double-count asks.
Deny hints
When a tool is denied, deny_hint() generates the narrowest possible allow rule based on the tool type and input:
Bash→(exec "<binary>" *)using the first word of the commandRead/Write→(fs read/write (subpath "<parent>"))from the file pathWebFetch→(net "<domain>")extracted from the URL- Fallback → bare verb shortcuts (
clash allow bash, etc.)
Color forcing
The render function calls console::set_colors_enabled(true) because Claude Code pipes the status line command's stdout (not a TTY), but the TUI does support ANSI escape codes.