Where phux keeps its socket, config, state, logs, and credentials.
phux splits its files across the three XDG base directories: config (hand-written, follows you between machines), runtime (expected to disappear on reboot), and state (survives across runs but is not config). Paths below are symbolic; each rule names its fallback when the environment variable is unset.
Profiles
Every runtime and state path carries the active profile, so a development build cannot touch the installed build’s socket, sessions, or logs. The profile is $PHUX_PROFILE when set; otherwise dev for a binary that is not an installed release (a debug build, or one under a Cargo target/ directory); otherwise default.
The default profile is unsuffixed — phux — so paths created by earlier releases stay valid. Any other profile suffixes the directory name: phux-dev, phux-<name>. Below, <profile-dir> means phux or phux-<profile> accordingly. phux doctor reports the resolved profile.
Socket
The Unix domain socket every consumer dials and the server binds:
$PHUX_SOCKETif set (an explicit--socketflag still overrides it);$XDG_RUNTIME_DIR/<profile-dir>/phux.sockifXDG_RUNTIME_DIRis set;/run/user/<uid>/<profile-dir>/phux.sockifXDG_RUNTIME_DIRis unset and that login runtime directory exists and is owned by this user (sessions such as Tailscale SSH or cron that omit the variable still reach the systemd--userserver);/tmp/phux-<user>[-<profile>]/phux.sockotherwise.
The parent directory is created mode 0700. Its sibling spawn.lock serialises auto-spawn so concurrent invocations elect one server rather than racing to bind.
A socket file whose server has died is stale, not a server: liveness is established by connecting, never by the file existing. A stale entry is removed and a server started, so an uncleanly killed server cannot wedge later invocations.
Config
$XDG_CONFIG_HOME/phux/config.toml, falling back to ~/.config/phux/config.toml when XDG_CONFIG_HOME is unset. A missing config file is not an error (the embedded defaults apply); there is no global config-path flag — set XDG_CONFIG_HOME to isolate configuration for a test or an alternate environment. phux config path prints the resolved path.
Beside config.toml, ops.env holds the ideas-capture credentials phux idea reads: PHUX_IDEAS_KEY_ID (the key id) and PHUX_IDEAS_KEY (64 hex characters, the 32-byte HMAC secret). The secret is never printed. The file is not config.toml and is never committed.
Beside it, known-authorities holds the certificate authority each paired server’s leaf was issued by, one line per server keyed by the [[remote]] entry’s cert-fingerprint. Pairing writes it (phux host add, --code); a connection never does. A server presenting another CA is refused. Delete a line to forget a pin.
State
The state directory is $XDG_STATE_HOME/<profile-dir>, falling back to ~/.local/state/<profile-dir> when XDG_STATE_HOME is unset or empty:
$XDG_STATE_HOME/<profile-dir>/
├── server.log # the ONE server log, both spawn paths
├── server.log.1..4 # older generations, rotated while live
├── server-starts.log # one line per server start (crash-loop check)
├── client-<pid>.log # per-pid interactive-client log
├── onboarding.json # versioned first-use journey progress
├── onboarding.lock # serializes first-use moment delivery
├── plugin-runs.jsonl # recent plugin action/hook/startup runs (phux plugin log)
├── plugin-startup/ # [[startup]] sidecar logs, pidfiles, and state
├── remote-cert.pem # auto-provisioned remote-consumer certificate (leaf, then CA)
├── remote-key.pem # its private key (owner-only, 0600)
├── remote-tokens # structured credential store (owner-only, 0600)
├── enrollment-tickets # single-use enrollment ticket hashes (owner-only, 0600)
├── reports/ # local bug-report bundles (TUI C-a B, phux report)
├── ideas/ # phux idea spool, one file per unsent capture
├── service-wrapper.sh # `phux service install --restore` wrapper
└── workspace.json # `--restore` archive, kept current by `--autosave`
server.logis the canonical server log regardless of how the server was started: the auto-spawn path redirects the daemon’s stderr here, and the service unit points its log capture at the same file.phux logsandphux service logsread it;PHUX_LOGtees the server’s structured log to an additional file without moving this one. Whenever it exceeds 8 MiB it is rolled aside toserver.log.1(older generations shifting to.2,.3,.4, oldest dropped), checked at server start and again periodically for as long as the server runs — so one very long-lived server is bounded the same as many short-lived ones. Rotation truncates the live file in place rather than replacing it, so atail -for the OS-redirected stdio a service-managed server writes through keeps working across it.server-starts.logrecords<epoch> <pid> <version>per server start.phux doctorcounts recent entries to report a crash-loop, and compares the newest version against the running binary’s to detect an upgrade the server has not picked up.client-<pid>.logis where an interactive client writes its trace — the TUI owns the alt screen, so the client never logs to stderr.PHUX_LOGredirects it. Log files are created mode0600.onboarding.jsonrecords only the versioned first-use journey stage.onboarding.lockserializes delivery within that profile. State is best-effort: missing state starts the guidance, while unreadable, unknown, or unwritable state stays quiet and never prevents attach.plugin-runs.jsonlis the bounded run log behindphux plugin log: one JSON line per plugin action run (TUI keybinding,phux config run, the MCP tool), server hook run, or[[startup]]lifecycle line, with exit status, duration, and the last 4 KiB of stdout and stderr. Writers lock it while appending and compact it to the newest 100 runs once it passes 1 MiB. Recording is best effort and never fails a run.plugin-startup/holds each[[startup]]sidecar:<plugin-id>/<startup-id>.log(stdout and stderr, mode0600, rotated at 1 MiB with three generations),<plugin-id>/<startup-id>.<server-pid>.pid(so the next start can reap an orphan), and<plugin-id>/<startup-id>/(PHUX_PLUGIN_STATE, mode0700).phux plugin log --startupprints the log tail.remote-cert.pem/remote-key.pemare the TLS pair auto-provisioned for remote consumers: issued by the workload CA (workload-ca.pem), the certificate file holding the leaf then the CA, or self-signed when provisioned before that.PHUX_WS_TLS_CERT/PHUX_WS_TLS_KEYsubstitute an operator-supplied pair. A complete pair is never regenerated (phux workload authority --rotatereplaces it on purpose), so the pinned fingerprint stays stable — which also means its subjectAltName set is fixed at generation;phux doctorreports whether it names the address phux advertises, and (remote-cert-chain) whether an auto-provisioned leaf still fails to chain to the workload CA, which blocks phone QR enrollment.remote-tokensis the versioned verifier-only credential store the server reads andphux pair,phux pair rotate, andphux pair revokeupdate under the sibling.remote-tokens.lock. Writers first lock the owner-controlled, non-group/world-writable parent directory, then no-follow open and validate the owner-only regular lock file, preventing lock-path replacement from splitting concurrent writers. Store commits use a synced temporary file and atomic rename. The store must be a regular, non-symlink file owned by the effective user with no group/world permissions; an integrity failure denies authentication.PHUX_WS_TOKENSmoves it without weakening those checks. A store of legacy anonymous token lines is refused: remove it and pair again.enrollment-ticketsholds the SHA-256 of each single-use ticketphux pair --enrollminted (never the ticket), with the scope and lifetime the credential it enrolls gets; the first redemption consumes it. It sits beside the workload registry and moves withPHUX_WORKLOAD_KEYS.ideas/is thephux ideaspool (ideas/spool/, one file per idea that does not yet have a 2xx). The file keeps the idempotency key. A network failure leaves it; the nextphux ideasends those same bytes. A 2xx removes it.reports/holds local bug-report bundles written by the TUIreport-bugaction (C-a B) and byphux report new. Each subdirectory is one report (session, pane, version, log tails, optional screen dump, and areport.mdan agent can open).latestpoints at the newest;phux reportlists them andphux report showprints one.service-wrapper.shandworkspace.jsonexist only underphux service install --restore. The wrapper startsphux server --autosave workspace.jsonand saves once more on stop. The server restores the archive on a fresh start and rewrites it (owner-only,0600) through a syncedworkspace.json.autosave.tmpand an atomic rename a few seconds after the workspace changes, so a crash or power loss leaves the latest layout. An archive that fails to restore is copied toworkspace.json.unrestoredfirst.
Decided: not built
A server.pid file and a journal/ directory of per-pane PTY output for crash recovery are decided: not built. Neither path exists today; a crash loses every pane’s scrollback. The EVENT stream is a separate, memory-bounded journal that carries no PTY bytes. Workspace archives are written only where phux archive export or phux server --autosave is pointed.