Skip to content

Troubleshooting

Eric Kochen edited this page Jun 6, 2026 · 84 revisions

purple writes structured logs to ~/.purple/purple.log. This page is a complete reference for diagnosing every kind of failure: connection, sync, password, certificate, container, tunnel, snippet, MCP, theme, persistence, rendering. Each section names the exact log strings to grep for and the concrete fix.

Quick orientation

Before you dig into the log, run these three commands:

purple logs              # prints the log path (~/.purple/purple.log)
purple logs --tail       # follows the log in real time
purple --verbose         # restart purple with debug-level logging

If you opened an issue or asked someone for help, attach:

  1. The startup banner (top of the log, see Startup banner).
  2. The 50 lines around the failure.
  3. Your OS and terminal: uname -a, echo "$TERM $COLORTERM".
  4. The output of ssh -V.

Never paste the entire log without redaction. The log contains host aliases and provider names. Tokens, passwords and TLS certificates are not logged on purpose (see What is never logged), but it is good hygiene to scan once before sharing.

Log file

The log file is created automatically on first run. It rotates at 5 MB. The previous file is renamed to purple.log.1 and the new file starts empty. There is exactly one backup. Older content is discarded.

purple logs              # print the log file path
purple logs --tail       # follow in real time (uses tail -f)
purple logs --clear      # delete the log file (next run starts fresh)

When ~/.purple/ is not writable (read-only home, hardened user, container without HOME) purple still runs but writes no log. The TUI shows status normally. The fix is to create ~/.purple/ with mode 700 or set HOME to a writable directory.

Log levels

By default only warnings and errors are logged. The signal-to-noise ratio is intentionally high so a default install never floods the disk.

Raise verbosity in one of two ways:

Mechanism Effect Use when
--verbose flag Sets level to debug Reproducing one bug from a fresh start
PURPLE_LOG=trace|debug|info|warn|error|off env var Overrides everything Persistent verbose mode without retyping the flag

The env var takes precedence over the flag, including PURPLE_LOG=off which silences logging completely.

purple --verbose                  # debug level (most useful default)
PURPLE_LOG=trace purple           # maximum detail (slow on large configs)
PURPLE_LOG=off purple             # disable logging entirely

Debug-level logs include every state transition (toast routing, queue drops, filter changes, container refresh batch lifecycle, vault thread spawn and join). Trace-level adds nothing extra in purple itself but downstream libraries occasionally emit at trace.

Fault domains

Warnings and errors carry a prefix that identifies the source of the problem. The prefix is the first thing to grep for when you do not know where to look.

Prefix Meaning Examples
[external] Remote host or third-party tool SSH exit code, SCP failure, Vault unreachable, provider API error, container runtime missing on remote, tmux subprocess failed
[config] Local configuration issue Missing password manager binary, SSH key permissions, unrecognized config line, theme file invalid, preferences write failed, provider config parse error
[purple] Internal error in purple itself Atomic write failed, SSH config write failed, snippet thread panicked, history save failed, vault sign thread spawn failed

Info and debug messages are operational flow markers: they describe what purple did, not what went wrong. Many of them carry the same [external]/[config]/[purple] prefix as warnings and errors (for example the [purple] apply_filter:, [purple] refresh_batch: and [config] reload_hosts: lines referenced below), so a prefix alone does not mean a line is a problem. Filter by WARN/ERROR when you only want failures.

Quick grep recipes:

grep '\[external\]' ~/.purple/purple.log     # blame: remote side
grep '\[config\]'   ~/.purple/purple.log     # blame: local environment
grep '\[purple\]'   ~/.purple/purple.log     # blame: purple itself
grep -E 'WARN|ERROR' ~/.purple/purple.log    # anything that needs attention

Startup banner

Every session writes a banner to the log file with diagnostic context. The banner bypasses level filters: even with PURPLE_LOG=off the banner is still written. This guarantees a support bundle always has the environment context.

--- purple v3.22.0 started at 2026-06-06 09:30:00Z ---
    os=macos arch=aarch64 config=/Users/you/.ssh/config
    ssh=OpenSSH_9.6p1, LibreSSL 3.3.6
    term=xterm-256color colorterm=truecolor
    theme=Purple
    hosts=42 patterns=3 snippets=7
    providers=aws,hetzner,tailscale
    askpass=keychain,vault:secret/ssh#password
    proxy_env=none
    vault_ssh=enabled (addr=https://vault.example.com:8200)
    log_level=debug
Field What to check
os / arch Platform-specific bug (macOS keychain, Linux SELinux, etc.)
config SSH config path actually being read
ssh ssh binary version. Old OpenSSH (<8.0) lacks features purple expects
term / colorterm Color tier detection (NO_COLOR / ANSI 16 / truecolor)
theme Active theme. Mismatch with expectations means preferences load failed
hosts / patterns / snippets Parsed counts. Zero on a non-empty config means parse error elsewhere
providers Configured cloud providers. Empty means none configured
askpass Password sources in use. Helps diagnose auth flow
proxy_env HTTP/HTTPS/ALL/NO_PROXY env vars in effect (names only, never values)
vault_ssh Vault SSH secrets engine integration state and resolved address
log_level Effective verbosity for this session

Connection issues

SSH connect fails immediately

Look for [external] SSH connection failed: <alias> (exit <code>) (interactive shell) or [external] SSH exec failed: <alias> (exit <code>) (one-shot command). Common exit codes:

Exit Meaning Fix
1 Generic SSH error Read the captured stderr in the next log line
2 Misuse of command Usually a malformed alias or option in ~/.ssh/config
5 Permission denied (publickey) Check IdentityFile, key permissions (chmod 600), key loaded in agent
124 Timeout Network or firewall. Try ssh -v <alias> for the SSH handshake trace
255 SSH could not connect at all DNS, route, firewall. ping, traceroute, ssh -v

Host key mismatch / man-in-the-middle warning

Look for [external] Host key CHANGED detected. The remote host's identity changed since the last connection. Either the host was re-installed or someone is intercepting.

Safe response when you trust the change (re-install, new VM, IP recycled):

ssh-keygen -R <hostname>
ssh-keygen -R <ip-address>

Then reconnect. purple offers an inline reset prompt for some flows (Containers tab); the dialog runs the same ssh-keygen -R for you.

Host key unknown / first connect

Look for [external] Host key UNKNOWN detected. On the first connection SSH asks you to accept the new key. purple shows a toast: "Host key unknown. Connect first (Enter) to trust the host." Press Enter from the host list to launch an interactive SSH session that will prompt for trust.

tmux integration fails

Look for [external] tmux new-window failed for <alias> (exit <code>) or [external] tmux exec window failed. Causes:

  • No tmux session running. Start one first (tmux new -s work) or open purple outside of tmux.
  • tmux server crashed. tmux kill-server and start fresh.
  • An outdated tmux. Update to a current release and retry.

Connection hangs at "Connecting..."

Look for [purple] container exec queued or [purple] cli tunnel start: alias=<alias> lines and check if the next event ever fires. If purple queues the work but no completion arrives, the underlying ssh is blocked. Causes:

  • TCP timeout (host unreachable, slow DNS).
  • Server prompting for input outside purple's askpass flow (StrictHostKeyChecking, password fallback).
  • ProxyJump chain stalled.

Workaround: open a vanilla ssh -v <alias> in another terminal to see the prompt or the hang point.

Provider sync

Provider sync returns 0 hosts

Look for [config] Provider sync returned 0 hosts: <name> (check API token permissions). Common causes:

  • API token has read scope but lacks the specific permission the provider requires (e.g. AWS ec2:DescribeInstances, Hetzner read-only token without server list).
  • Region filter excludes all instances. Check --regions you passed when configuring the provider. Remove the filter (purple provider add <name> --regions "") to fetch all regions and confirm.
  • Account is empty (correct outcome). Spin up a test VM and re-sync.

Provider sync partial: some hosts missing

Look for [external] Provider sync partial: <name>, N hosts, M failures. Some pages or regions failed. purple keeps the partial result and refuses --remove so a flaky API does not wipe your config. Re-run sync once the provider recovers.

Detail per failure is logged at debug level just above this line. Look for [external] HTTP 4xx/5xx or [external] Request failed.

Provider sync HTTP 401 / 403

Look for [external] HTTP 401: authentication failed or HTTP 403. The token is invalid, expired or revoked. Fix:

purple provider remove <name>
purple provider add <name> --token <new-token>

For AWS, check ~/.aws/credentials or the AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY env vars. Set --profile explicitly in purple provider add aws to pin a profile from ~/.aws/credentials.

Provider sync HTTP 429: rate limited

Look for [external] HTTP 429: rate limited. The provider throttled the request. Wait, then re-run the sync after the provider's cool-off window (varies per provider). purple does not auto-retry a throttled sync: it surfaces the 429 and leaves your existing hosts untouched.

Unknown provider

Look for [config] cli sync: skipping unknown provider '<name>' (CLI sync) or [config] Unknown provider requested for sync: <name> (TUI), or [config] Skipping invalid section header [<header>]. Your ~/.purple/providers file references a provider purple does not know (typo, removed provider, future-only provider). Fix by editing the file or running purple provider list to see what is configured.

Password retrieval

Password manager binary not found

Look for [config] Password manager binary not found: <tool>. Tools purple integrates with:

Source CLI binary Install
op:// (1Password) op brew install 1password-cli
pass: (pass) pass brew install pass or distro package
bw: (Bitwarden) bw brew install bitwarden-cli
proton: (Proton Pass) pass-cli curl -fsSL https://proton.me/download/pass-cli/install.sh | bash
vault: (Vault KV) vault Official HashiCorp installer
keychain (OS keychain) built-in n/a

keychain is the only source without a trailing colon. it has no payload argument because the lookup key is the host alias itself. All other sources use prefix:argument form (e.g. vault:secret/ssh#password).

Fix: install the binary, then verify with which op (or the relevant tool). purple does not need a configuration step beyond that.

Bitwarden vault is locked

Look for [external] Password retrieval failed via bw:. Bitwarden vaults stay locked between sessions. Unlock first:

bw login                 # only once per machine
bw unlock                # captures session token
export BW_SESSION=$(bw unlock --raw)
purple

purple inherits the BW_SESSION env var. When it is missing or expired, retrieval fails silently with a warning toast and falls back to the SSH password prompt.

Vault KV secrets engine path returns nothing

Look for [external] Password retrieval failed via vault:secret/.... The vault: askpass prefix queries the Vault KV secrets engine (distinct from the Vault SSH secrets engine; see Vault SSH Certificates for that). Check:

vault status                                  # Vault reachable and unsealed?
vault kv get secret/ssh                       # the path your askpass points at

vault: paths use #field syntax: vault:secret/ssh#password reads the password field. Mistyped field names produce empty strings, which purple treats as retrieval failure.

Proton Pass is not logged in

Look for [external] Password retrieval failed via proton:.... The proton: askpass prefix calls pass-cli item view --vault-name Vault --item-title Item --field field. purple runs pass-cli test before every connect and prompts for a Personal Access Token on stdin when not authenticated. If the prompt was dismissed, or you connect from a non-interactive context where the PAT prompt cannot complete (a script with no stdin, an automation runner, an ssh -F invocation that bypasses purple), log in manually:

pass-cli login                                                       # default: web sign-in (opens a URL)
pass-cli login --interactive                                         # username + password on the terminal
PROTON_PASS_PERSONAL_ACCESS_TOKEN=pst_xxx... pass-cli login          # headless
pass-cli item view --vault-name Vault --item-title Item --field password   # verify the lookup path

proton: paths use the Vault/Item/field form (no leading slash, no scheme). purple maps this to name-based lookup flags (--vault-name, --item-title, --field) on every retrieval, so Proton's internal share/item IDs never leak into your SSH config.

SSH key permission issue

Look for [config] SSH key permission issue. SSH refuses keys with loose permissions. Fix:

chmod 600 ~/.ssh/id_*           # private keys: owner-only
chmod 644 ~/.ssh/id_*.pub       # public keys: world-readable is fine
chmod 700 ~/.ssh                # directory itself: owner-only

Vault SSH certificates

purple integrates two distinct HashiCorp Vault secrets engines. Do not confuse them when troubleshooting:

Engine Purpose purple integration
Vault SSH secrets engine Signs SSH public keys into short-lived certificates purple vault sign, # purple:vault-ssh <role> per host, Vault SSH role per provider
Vault KV secrets engine Stores arbitrary key/value secrets vault: askpass prefix (password source)

This section covers the Vault SSH secrets engine. For Vault KV password retrieval see the previous section.

Vault unreachable / connection refused / timed out

Look for any of the following Vault SSH signing errors. They surface as toasts; in the log, connection problems appear under [external] Vault unreachable: and auth problems under [external] Vault auth failed:, with [external] Vault SSH signing failed: for anything unclassified:

  • Vault SSH connection refused.. server is not accepting connections (down, wrong port).
  • Vault SSH connection timed out.. network path or firewall.
  • Vault SSH timed out. Server unreachable.. long-running operation aborted.
  • Vault SSH host not found.. DNS resolution failed.
  • Vault SSH server uses HTTP, not HTTPS. Set address to http://.. protocol mismatch.
  • Vault SSH TLS error. Check certificate or use http://.. self-signed cert or expired cert.

Verify in this order:

echo $VAULT_ADDR              # is it set? required unless overridden
vault status                  # reachable, unsealed, initialized

purple resolves the Vault address in this precedence order, from highest to lowest: --vault-addr CLI flag, # purple:vault-addr <url> per-host comment, provider-level vault_addr default, VAULT_ADDR env var. The startup banner shows the resolved address under vault_ssh=enabled (addr=...).

Vault auth failed / token expired

Look for Vault SSH token missing or expired. Run \vault login`.orVault SSH permission denied. Check token and policy.`. Vault tokens have TTL. Re-authenticate:

vault login                   # interactive
# or
VAULT_TOKEN=$(...) purple     # one-shot

For Vault Agent / approle setups, refresh the token file purple's vault CLI reads.

Vault SSH role missing or misconfigured

Look for [external] Vault SSH signing failed: <reason> with a generic reason (the stderr scrubber strips secrets). Causes:

  • Role name typo: check # purple:vault-ssh <mount>/sign/<role> on the host (or provider vault_role default). The role must be in mount/sign/role form.
  • Role does not exist on the Vault server: vault read <mount>/roles/<role> on the Vault side.
  • Public key not allowed by the role: the role's allowed_users or key_type rejects what purple sent.

The signed certificate is cached under ~/.purple/certs/<alias>-cert.pub. Inspect it with ssh-keygen -L -f ~/.purple/certs/<alias>-cert.pub to see what was actually signed.

Vault SSH cert cache hit and miss

Look for Vault SSH certificate cache hit: alias=... role=... path=... (info-level) or Vault SSH certificate cache miss: alias=... role=... status=... -> signing (debug-level). The cache hit means an existing cert at check_path is still valid and was reused. A miss triggers re-signing when the status is Missing, Expired, Invalid, or a Valid cert with less time left than the renewal threshold.

If you expect a fresh cert but always see cache hit, delete the cached file: rm ~/.purple/certs/<alias>-cert.pub.

See Vault SSH Certificates for setup, policy and role configuration.

Container management

Docker or Podman not found on remote host

Look for [external] Container fetch failed: alias=<alias>: Docker or Podman not found on remote host. purple SSHes in and runs docker ps or podman ps; neither exists in $PATH. Fix on the remote host:

# Debian/Ubuntu
sudo apt-get install docker.io
sudo usermod -aG docker $USER

# RHEL/Fedora/Rocky
sudo dnf install podman

Permission denied on the remote docker socket

Look for [external] Container fetch failed: alias=<alias>: Permission denied. Is your user in the docker group? The SSH user lacks access to the docker daemon socket. Fix:

sudo usermod -aG docker <user>     # add to group
# log out and back in so the group takes effect

For Podman, rootless mode does not need a group; check that podman info works as the SSH user.

Container daemon not running

Look for [external] Container fetch failed: ...: Container daemon is not running. Start the service on the remote:

sudo systemctl start docker        # or podman.socket for rootful Podman

Container action complete is not visible

When you press K (restart) or S (stop) on a container, the result lands as a green Success toast. Look for Container restart complete. or Container stop complete. in the log. Missing entry usually means the SSH call failed; check the preceding line for [external] Container <action> failed: alias=... (the action verb is interpolated, e.g. Container restart failed).

Since v3.10 the result routes to the Success toast because the action is user-initiated.

Refresh batch hangs

Look for [purple] refresh_batch: alias=... done=X/Y in_flight=Z queue=Q spawned_next=N. Each completion logs one line. If in_flight > 0 but no new lines arrive, an SSH child is stuck. Either kill the ssh child manually (pkill -f "ssh.*<alias>") or wait for the per-host TCP timeout.

SSH tunnels

Tunnel start fails immediately

Look for [purple] cli tunnel start: alias=<alias> followed by no progress. Then look for the SSH exit code. Common causes:

  • The host has no LocalForward/RemoteForward/DynamicForward directives configured. Add one with purple tunnel add <alias> L:8080:localhost:80 or via the TUI.
  • The local port is already bound (another process or a stale tunnel). Find it with lsof -nP -iTCP:<port> -sTCP:LISTEN and free it.
  • The remote does not allow port forwarding. Set AllowTcpForwarding yes on the SSH server.

Tunnel start: no forwards for alias

Look for [purple] cli tunnel start: no forwards for alias=<alias>. You ran purple tunnel start <alias> but the host has zero forwarding directives. Add one first.

Tunnel listed but never connects

The TUI shows the tunnel as configured but the connection dot stays dim. The tunnel path runs ssh -N under the hood. Failures surface as [external] tunnel start failed: alias=<alias>: <error>. Often this is a ProxyJump chain timing out or the remote refusing the forward.

File transfer (SCP)

SCP transfer failed

Look for [external] SCP transfer failed: <alias> exit=<code>. The exit code comes straight from the scp binary; OpenSSH does not publish a stable code table, so treat any non-zero value as "something went wrong" and read the captured stderr in the toast or the preceding log line.

Common failure modes:

  • Permission denied on the destination (read-only filesystem, missing directory).
  • Source file missing or unreadable locally.
  • Connection lost mid-transfer (network instability).
  • Remote disk full.

The file browser overlay shows the same error inline; the log adds the exact exit code so you can correlate with man scp exit semantics for your platform.

Snippets

Snippet fails on one host out of many

Look for [purple] Snippet stdout reader thread panicked or [purple] Snippet stderr reader thread panicked. The reader thread for one target host died. Other hosts continue. Causes:

  • Remote command produced binary output that broke UTF-8 assumptions.
  • SSH child exited mid-stream.

Inspect the affected host's stderr in the snippet output overlay; the captured tail is shown verbatim.

Snippet run does not start

Look for [purple] cli snippet: dispatch followed by nothing. The dispatcher hit an early-exit branch (no host, snippet name typo). Check the next stderr line.

Themes

Custom theme does not load

Look for [config] Invalid theme file: <path>. Custom themes live in ~/.purple/themes/*.toml. Parse failure causes purple to fall back to the previous (or default) theme. Validate the TOML with cat ~/.purple/themes/<name>.toml | python -c 'import sys,tomllib; tomllib.loads(sys.stdin.read())' or any TOML linter.

The startup banner shows theme=<name>. If the name matches your custom theme but colors look wrong, the file parsed but the color tier mismatched: a custom theme that only defines truecolor slots will render flat on an ANSI-16 terminal.

Colors look wrong (washed out, missing)

Check the banner: colorterm=truecolor is required for the full palette. colorterm=unset or anything other than truecolor/24bit collapses purple to ANSI 16. Fix:

echo $COLORTERM            # check what your terminal sets

If your terminal supports truecolor but the env var is missing, export it from your shell init: export COLORTERM=truecolor. Common terminals that support truecolor: iTerm2, Alacritty, WezTerm, Kitty, modern xterm, modern tmux (with tmux -2 or set-option -sa terminal-features ',xterm-256color:RGB').

NO_COLOR mode

Setting NO_COLOR=1 (per no-color.org) collapses purple to modifiers-only (bold, dim, no color). Helpful for screen readers and accessibility-first workflows. The banner shows the corresponding effective tier.

Preferences and persistence

Preferences do not stick across sessions

Look for [config] failed to save <key>=<value>: <error>. Every preference write logs a debug line before the save and a warn on failure. The common causes:

  • ~/.purple/ directory is read-only or not writable by the current user.
  • Disk full or filesystem mounted read-only.
  • File system permissions blocking atomic rename (atomic_write uses temp file + rename; rename fails on cross-filesystem boundaries).

Each preference has its own debug line:

  • [purple] saving sort_mode=<value>
  • [purple] saving group_by=<value>
  • [purple] saving view_mode=<value>
  • [purple] saving containers_sort_mode=<value>
  • [purple] saving containers_view_mode=<value>
  • [purple] saving containers_collapsed_hosts=<csv> (N aliases)
  • [purple] saving askpass default=<value>
  • [purple] saving slow_threshold_ms=<value>
  • [purple] saving theme=<value>

If the debug line is there but no warn follows, the write succeeded. If both are missing, the code path was never reached. Check the keybinding or screen handler.

SSH config write failed

Look for [purple] SSH config write failed: <path>: <error> or [purple] Atomic write failed: <path>: <error>. Causes:

  • ~/.ssh/config is read-only.
  • Disk full.
  • ~/.ssh/config is a symlink whose target is on a different filesystem (atomic rename fails).
  • Another process holds an advisory lock (purple uses flock; concurrent writes block until the lock releases).

purple keeps the last 5 backups: ~/.ssh/config.bak.<timestamp>. Restore with cp ~/.ssh/config.bak.<latest> ~/.ssh/config if a write corrupted the file.

Externally-edited SSH config detected

Look for [config] check_config_changed: mtime drift detected on <path> -> reloading. Someone (you, another purple instance, an editor) modified ~/.ssh/config while purple was running. purple reloads automatically and clears ping results. When the screen is a form, picker or confirm overlay, the reload is deferred until you close the overlay.

For the Vault SSH bulk sign path, an external edit during a long-running sign batch is detected separately. Look for External ssh config edits detected, merged N CertificateFile directives (good) or External ssh config edits detected; certs on disk, no CertificateFile written (the sign succeeded but the directive merge was skipped to avoid clobbering your edit).

TUI rendering

Search bar / filter does not respond

Look for [purple] apply_filter: query=... down_only=... scope=.... The function logs every invocation. Missing entry means the keystroke did not dispatch the filter handler. Use PURPLE_LOG=trace to capture every key event. Common cause: a sticky toast is consuming the key (rare).

Sort or group change does not respond

Look for [purple] apply_sort: mode=... group_by=... hosts=.... Same pattern as filter. Missing entry means the keystroke dispatched somewhere else (focus on a search bar instead of the host list, for example).

Toast message does not appear

Look for [purple] background status suppressed (sticky active, dropped: <text>). When a sticky message is on screen (Vault SSH progress, sticky error, container refresh progress) purple suppresses lower-priority background toasts so the sticky stays visible. Press a key or wait for the sticky to clear, then the next message routes normally.

User-initiated toasts (Success on host save, Warning on validation failure) are never suppressed. Only Info background messages are.

Form submit hangs

Look for [purple] host form submit: screen=... alias='...' is_pattern=... followed by either:

  • [purple] host form submit: external config change, aborting (mtime drift; reopen the form),
  • [purple] host form validate failed: <reason> (validation rejected; the toast shows the reason), or
  • success (no follow-up, the form closes).

A submit that produces no log line means the Enter key never reached the submit handler. Run with PURPLE_LOG=trace and check whether the keystroke dispatched to a picker or input bar.

MCP server

MCP audit log not written

Look for [purple] Could not determine home directory; MCP audit log disabled. Set --audit-log <PATH> explicitly to enable auditing. This means dirs::home_dir() failed. Set the path explicitly:

purple mcp --audit-log /tmp/mcp-audit.log

Or disable auditing entirely with purple mcp --no-audit.

MCP tool denied

Look for Tool denied. Server started with --read-only. Restart without --read-only to enable state-changing tools. You started the MCP server with --read-only. Stop and restart without the flag if you want write capability. Read-only mode is recommended when exposing purple to autonomous agents; only relax it for trusted clients.

MCP SSH timeout

Look for [external] MCP SSH command timed out after <N>s (pid <pid>). The remote command exceeded the per-call timeout. Either the command is genuinely slow or the SSH connection stalled. For long-running commands the MCP server is not the right interface; use purple snippet or a regular interactive session.

Demo mode

Demo mode is purple running on synthetic data (purple --demo). Connection, sync, signing, file transfer and container actions are disabled and surface yellow Warning toasts instead of executing. The startup banner shows the same fields with providers= listing the synthetic providers.

Demo mode logs [purple] ssh_config.write skipped (demo mode) for every config write that would have happened, so you can verify which paths trigger writes in normal operation.

Configuration files

File Purpose Recovery
~/.ssh/config Source of truth for hosts Backups at ~/.ssh/config.bak.<timestamp> (5 most recent)
~/.purple/preferences UI preferences (sort, theme, view, askpass default) Delete to reset to defaults
~/.purple/providers Cloud provider configurations Backups not kept. Re-add via purple provider add
~/.purple/snippets Saved snippets (INI format) Recreate via TUI or purple snippet add
~/.purple/container_cache.jsonl Last-seen container list per host Safe to delete. Will refetch on next visit
~/.purple/certs/<alias>-cert.pub Vault SSH signed certificates Safe to delete. Will re-sign on next connect
~/.purple/themes/*.toml Custom themes User-managed, not autosaved
~/.purple/history.tsv Connection history (frecency) Safe to delete. Sort order resets
~/.purple/sync_history.tsv Per-provider sync result history Safe to delete. Sync banner timing resets
~/.purple/recents.json Jump bar recent picks (cap 50) Safe to delete. Jump empty-state resets
~/.purple/key_activity.json Per-key SSH connection activity log (90-day retention) Safe to delete. Keys-tab sparkline resets
~/.purple/mcp-audit.log MCP server audit log Tail or rotate as needed
~/.purple/purple.log This log file purple logs --clear

What is never logged

By design, the log file never contains:

  • API tokens, Vault tokens, SSH passwords. The [external] Password retrieval failed lines never include the retrieved value.
  • Vault stderr is scrubbed before logging: any line containing "token", "secret", "x-vault-", "cookie" or "authorization" is filtered.
  • Clipboard contents. The Copied <name> toast never includes the data.
  • Snippet stdout. Only metadata (host, exit code, lines emitted) appears in the log; the actual output is shown in the TUI overlay and discarded when you close it.

Proxy env var names are recorded in the startup banner but values are not (proxy URLs can contain user:password@host credentials).

When in doubt, grep your shared snippet for token=, password=, secret= before sharing.

Performance

Slow startup

The startup banner logs the time of session start. If purple takes more than 1-2 seconds to draw the first frame, look at:

  • Number of hosts. The banner's hosts= field shows the parsed count. Configs with 10000+ hosts can stress the parser; check [config] reload_hosts: hosts=N patterns=M display_items=K to see what was loaded.
  • ssh -V detection has a 2-second hard timeout. A broken ssh binary causes a one-time 2s delay.
  • Provider autosync runs in the background and does not block startup. If you suspect autosync slowness, run purple --verbose and watch for [external] cli sync: starting provider=<name> entries.

Container refresh slow

Look for the [purple] refresh_batch: lifecycle. Each host's docker ps runs in parallel (up to 4 concurrent SSH calls). Slowness per host is the SSH round trip; reducing it requires faster SSH (e.g. ControlMaster reuse).

Getting help

If after reading this page the failure mode is still unclear:

  1. Reproduce with purple --verbose.
  2. Capture the startup banner and the failure window (50 lines around the WARN/ERROR).
  3. Scrub host aliases and provider names if they are sensitive.
  4. Open an issue on github.com/erickochen/purple/issues with the snippet and your platform info from uname -a and ssh -V.

For security disclosures, follow SECURITY.md instead of opening a public issue.

See also

Clone this wiki locally