Troubleshooting

Common issues and solutions. When in doubt, run tirith doctor for diagnostics.

Shell hooks not loading

Run tirith doctor to see the hook directory and whether hooks were materialized from the embedded binary.

If hooks are not found:

  1. Ensure tirith is in your PATH
  2. Run eval "$(tirith init)" and check for errors
  3. Set TIRITH_SHELL_DIR to point to your hooks directory explicitly

Brew upgrade applied but behavior didn't change

If brew reports a newer version but tirith --version is older:

shell
$ which -a tirith
$ brew info tirith
$ hash -r

Then refresh materialized hooks and restart shell:

shell
$ rm -rf ~/.local/share/tirith/shell
$ exec zsh # or exec bash / restart terminal

Bash: Enter mode vs preexec mode

Tirith supports two bash integration modes:

Enter mode (default outside SSH)

Binds to Enter key via bind -x. Intercepts commands and paste before execution. Includes startup health gate and runtime self-healing.

Preexec mode

Uses DEBUG trap. Compatible with more environments but warn-only — cannot block commands. No paste interception.

Set via environment variable (before tirith init in your shell rc):

shell
$ export TIRITH_BASH_MODE=enter # or preexec

Persistent safe mode

If enter mode detects a failure, it auto-degrades to preexec and writes a persistent flag. To re-enable:

shell
# Option 1: CLI reset
$ tirith doctor --reset-bash-safe-mode
 
# Option 2: explicit override in .bashrc (before tirith init)
$ export TIRITH_BASH_MODE=enter

Bash: no visible input after ssh / gcloud compute ssh

Tirith auto-defaults to preexec mode when SSH_CONNECTION, SSH_TTY, or SSH_CLIENT is set. If you still see input issues:

shell
$ export TIRITH_BASH_MODE=preexec
$ eval "$(tirith init --shell bash)"

PowerShell: PSReadLine conflicts

Ensure the tirith hook loads after PSReadLine initialization. The hook overrides PSConsoleHostReadLine to intercept pastes.

Latency

Tirith's Tier 1 fast path (no URLs detected) targets <2ms. If you notice latency:

  1. Run tirith check --json -- "your command" and check timings_ms
  2. If Tier 1 is slow, check for extremely long command strings
  3. Policy file loading (Tier 2) adds ~1ms. Use tirith doctor to see policy paths

False positives

If a command is incorrectly blocked or warned:

  1. Run tirith why to see which rule triggered
  2. Add the URL to your allowlist: ~/.config/tirith/allowlist
  3. Override the rule severity: severity_overrides: { rule_id: LOW }

Policy discovery

Tirith searches for policy in this order:

  1. TIRITH_POLICY_ROOT env var
  2. Walk up from CWD looking for .tirith/policy.yaml
  3. ~/.config/tirith/policy.yaml (user-level fallback)

Use tirith doctor to see which policy files are active.

Warp terminal: silent blocking

Warp handles /dev/tty output differently. Tirith auto-detects Warp, but if messages aren't showing:

shell
$ export TIRITH_OUTPUT=stderr

Codex: MCP protected, but direct shell commands still run

Codex has two execution paths. MCP gateway covers MCP tools/call, but native /bin/zsh -lc execution does not pass through MCP.

Fix: Ensure both paths are configured — the MCP gateway AND the ~/.zshenv guard. See the MCP Integration docs for details.

Unexpected tirith exit codes

Tirith uses a mixed fail-safe policy for unexpected exit codes (crashes, OOM-kills):

ContextBehavior
Bash enter modeAuto-degrades to preexec. Current command not executed.
Zsh / Fish / PowerShellWarns and executes. Terminal never breaks.
All paste pathsFail-closed — discards paste. You can re-paste.

Expected exit codes: 0 (allow), 1 (block), 2 (warn). Anything else is treated as unexpected.

Audit log location

Default: ~/.local/share/tirith/log.jsonl (XDG-compliant). Each entry is a JSON line with timestamp, action, rule IDs, and redacted command. Disable with export TIRITH_LOG=0.