-
-
Notifications
You must be signed in to change notification settings - Fork 14
Troubleshooting
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.
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 loggingIf you opened an issue or asked someone for help, attach:
- The startup banner (top of the log, see Startup banner).
- The 50 lines around the failure.
- Your OS and terminal:
uname -a,echo "$TERM $COLORTERM". - 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.
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.
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 entirelyDebug-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.
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 attentionEvery 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 |
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
|
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.
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.
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-serverand start fresh. - An outdated tmux. Update to a current release and retry.
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).
-
ProxyJumpchain stalled.
Workaround: open a vanilla ssh -v <alias> in another terminal to see the prompt or the hang point.
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
--regionsyou 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.
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.
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.
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.
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.
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.
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)
purplepurple 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.
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 atvault: paths use #field syntax: vault:secret/ssh#password reads the password field. Mistyped field names produce empty strings, which purple treats as retrieval failure.
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 pathproton: 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.
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-onlypurple 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.
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, initializedpurple 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=...).
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-shotFor Vault Agent / approle setups, refresh the token file purple's vault CLI reads.
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 providervault_roledefault). The role must be inmount/sign/roleform. - 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_usersorkey_typerejects 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.
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.
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 podmanLook 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 effectFor Podman, rootless mode does not need a group; check that podman info works as the SSH user.
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 PodmanWhen 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.
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.
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/DynamicForwarddirectives configured. Add one withpurple tunnel add <alias> L:8080:localhost:80or via the TUI. - The local port is already bound (another process or a stale tunnel). Find it with
lsof -nP -iTCP:<port> -sTCP:LISTENand free it. - The remote does not allow port forwarding. Set
AllowTcpForwarding yeson the SSH server.
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.
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.
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.
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.
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.
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.
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 setsIf 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').
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.
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_writeuses 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.
Look for [purple] SSH config write failed: <path>: <error> or [purple] Atomic write failed: <path>: <error>. Causes:
-
~/.ssh/configis read-only. - Disk full.
-
~/.ssh/configis 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.
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).
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).
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).
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.
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.
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.logOr disable auditing entirely with purple mcp --no-audit.
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.
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 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.
| 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 |
By design, the log file never contains:
- API tokens, Vault tokens, SSH passwords. The
[external] Password retrieval failedlines 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.
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=Kto see what was loaded. -
ssh -Vdetection has a 2-second hard timeout. A brokensshbinary causes a one-time 2s delay. - Provider autosync runs in the background and does not block startup. If you suspect autosync slowness, run
purple --verboseand watch for[external] cli sync: starting provider=<name>entries.
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).
If after reading this page the failure mode is still unclear:
- Reproduce with
purple --verbose. - Capture the startup banner and the failure window (50 lines around the WARN/ERROR).
- Scrub host aliases and provider names if they are sensitive.
- Open an issue on github.com/erickochen/purple/issues with the snippet and your platform info from
uname -aandssh -V.
For security disclosures, follow SECURITY.md instead of opening a public issue.
- CLI Reference for the complete command surface
- Cloud Providers for provider-specific setup notes
- Vault SSH Certificates for cert signing details
- Password Management for askpass source configuration
- MCP Server for AI agent integration
- Themes for theme customization
Getting started
Features
- Jump
- Cloud Providers
- File Explorer
- Command Snippets
- Password Management
- Vault SSH Certificates
- Container Management
- SSH Tunnels
- Keys
- Tags and Search
- Host Patterns
- Themes
- MCP Server
- Whats New
Reference