Where data lives#
PacketBench writes to one hidden directory under your home folder, one platform
log directory, the OS credential store, the webview's localStorage, and a
handful of files inside your own projects. Nothing else. This page is the
canonical list: every path, what owns it, and whether deleting it costs you
anything.
It supersedes the four partial tables that used to live on Install & first run, Core concepts, Issues & git hosts and Architecture internals. Where those disagreed, the correction is recorded at the end of this page.
The data directory is the same on every platform#
There is no per-platform application-data folder. DATA_DIR_NAME is
.packetbench (src-tauri/src/core/brand.rs:15) and it is joined onto the
user's home directory, resolved from USERPROFILE and then HOME
(src-tauri/src/core/shared.rs:16).
| Platform | Data directory |
|---|---|
| Windows | %USERPROFILE%\.packetbench\ |
| macOS | ~/.packetbench/ |
| Linux | ~/.packetbench/ |
That means a Windows install puts application state in your user profile root,
not in %APPDATA%. Only logs use a platform convention — see below.
One resolver can still adopt the pre-rename directory#
Most call sites join home/.packetbench directly. Four do not: the unified
state file, the provider-settings file, PTY transcripts and the PTY pid
registry resolve through core::storage::data_dir()
(src-tauri/src/core/storage.rs:309), which returns ~/.packetbench when it
exists and otherwise falls back to ~/.packetade — but only when that legacy
directory classifies as Ours
(src-tauri/src/core/migration.rs:82). The classifier exists because a sibling
packetcode TUI claimed the older name after the rename, and writing our state
into another product's home would scribble on someone else's data.
In normal operation the two resolvers agree, because startup migration creates
~/.packetbench before anything reads. They diverge only if the migration
failed and a legacy Ours directory survives: state and transcripts would land
in ~/.packetade while conversations and dictation land in ~/.packetbench.
See Upgrades & migration.
Everything in the data directory#
Source order is by subsystem. "Safe to delete" means with the app closed, and describes what you lose.
| Path | Owned by | Safe to delete |
|---|---|---|
state.v1.json |
src-tauri/src/core/storage.rs:94 — flights, issues, workspaces, servers, agent configs, settings, CLI accounts, memory events and patterns |
No. This is the backend authority for almost everything. |
state.v1.json.bak |
write_with_backup (src-tauri/src/core/storage.rs:343) |
Yes, but it is the only recovery copy if the primary is corrupted. |
state.v1.json.tmp |
Crash residue between the fsync and the rename-in (src-tauri/src/core/storage.rs:352) |
Yes, once the app has started cleanly. |
state.v1.json.corrupt-<unix> |
quarantine_corrupt (src-tauri/src/core/storage.rs:371) |
Yes, once you no longer need it for forensics. |
provider-settings.v1.json |
save_provider_runtime_settings (src-tauri/src/core/storage.rs:856) — provider base URLs and runtime overrides |
Yes. Providers revert to their defaults. |
conversations/<id>.json |
src-tauri/src/commands/conversations.rs:20 — one file per API-agent conversation, frontend-defined JSON that Rust treats as opaque |
Yes, per file. You lose that transcript. |
conversations/<id>/checkpoints/ |
Nothing. The checkpoint panel was Removed in 0.10.0 and upgraded installs still carry the leftovers (src-tauri/src/core/brand.rs:12) |
Yes. Nothing reads or writes them. |
pty-transcripts/<uuid>.log |
src-tauri/src/core/pty.rs:113. A sibling <uuid>.log.truncated marks a transcript that hit its cap |
Yes. Terminal history only. |
pty-active-pids |
src-tauri/src/core/pty.rs:130 — a flat file, one <pid>\t<command> line per PTY child spawned this run |
Yes when the app is not running. Deleting it while children are alive loses the orphan-reaping safety net for the next launch. |
dictation.db |
src-tauri/src/commands/dictation/history.rs:30 — dictation history and speaking analytics |
Yes. |
dictation.json |
src-tauri/src/commands/dictation/config.rs:106 — dictation settings, including the system-wide paste opt-in |
Yes. Settings revert to defaults, which means clipboard-only delivery. |
models/ggml-<size>.bin |
src-tauri/src/commands/dictation/models.rs:151 — Whisper weights, 75 MB to 3 GB each |
Yes. Re-downloadable from Settings → Dictation. |
models/ggml-<size>.bin.sha256 |
src-tauri/src/commands/dictation/models.rs:155 — the verified-checksum marker |
Only alongside its .bin. An orphaned marker forces a re-verify, which is the safe direction. |
usage.jsonl |
src-tauri/src/commands/usage.rs:47 — append-only token and cost ledger |
Yes, but the budget guardrails read it, so spend caps reset. |
provider-launches.json |
src-tauri/src/commands/provider_stats.rs:48 |
Yes. Launch counters only. |
sidecar-stats.json |
src-tauri/src/commands/agent_sidecar/status.rs:152 — lifetime sidecar restart and crash counters |
Yes. |
git-hosts.json |
src-tauri/src/commands/github.rs:186 — Gitea/Forgejo connection metadata, base URL and label only |
Yes. Connections must be re-added; the tokens stay in the keyring. |
github-token |
Nothing writes it. Legacy plaintext token read once at startup, copied to the keyring, then deleted (src-tauri/src/commands/github.rs:232) |
Yes. |
ssh/known_hosts |
src-tauri/src/commands/pty.rs:1381, path from src-tauri/src/core/execution.rs:12. Directory created mode 0700 on Unix |
No. Deleting it drops every pinned host key, and unpinned hosts fall back to trust-on-first-use. |
ssh-cm/ |
src-tauri/src/core/execution.rs:38 — SSH ControlMaster sockets, mode 0700, Unix only |
Yes when no SSH session is live. Windows OpenSSH has no ControlMaster, so this directory never appears there. |
commands/*.md |
You. PacketBench only reads it (src-tauri/src/commands/slash_commands.rs:119) — global slash commands |
Yours to manage. |
<command>-bin |
You. A pin file naming an absolute path for one CLI, read at spawn (src-tauri/src/commands/pty.rs:90) |
Yes. Resolution falls back to PATH. |
scratch/ |
src-tauri/src/commands/pty.rs:212 — the neutral working directory for a pane opened with no project |
Yes, recreated on demand. |
crashes/crash-<unix>.log |
write_crash_log (src-tauri/src/commands/crashes.rs:102) — panic message, location and backtrace |
Yes. Also deletable from Settings → Security & Diagnostics → Crash Reports. |
pty-active-pids is a file, not a directory. Earlier
documentation drew it with a trailing slash. It holds one tab-separated line
per child so the next launch can verify a pid's command basename still
matches before signalling it — a recycled pid is never killed.
known_hosts lives under ssh/, not at the root#
Two pages disagreed about this, and it matters: the wrong path sends you looking for a file that is not there when you are trying to work out why a host is unpinned.
The Rust source settles it. app_known_hosts_path() joins the data directory,
then ssh, then known_hosts:
pub fn app_known_hosts_path() -> PathBuf {
let home = home_dir().unwrap_or_else(|| ".".to_string());
PathBuf::from(home)
.join(crate::core::brand::DATA_DIR_NAME)
.join("ssh")
.join("known_hosts")
}
That is src-tauri/src/core/execution.rs:12-18, and it is the single
definition — ensure_known_hosts_dir (line 22), the ssh_pin_host write path
(src-tauri/src/commands/pty.rs:1381), the get_app_known_hosts_path command
returned to the frontend (src-tauri/src/commands/pty.rs:1412) and the
UserKnownHostsFile argument (src-tauri/src/core/execution.rs:113) all
resolve through it. The true path is:
~/.packetbench/ssh/known_hosts
install.md placed it at ~/.packetbench/known_hosts. That was wrong.
SSH remote workspaces and Settings were
correct.
The bare name known_hosts does appear once outside that directory, in the
migration marker list (src-tauri/src/core/migration.rs:30), where it is
matched as a directory entry name to identify a pre-rename data directory. It
is not a path.
Logs are the one platform-specific location#
Logs do not live in the data directory. dirs_log_dir()
(src-tauri/src/lib.rs:40) uses the platform convention, under
LOG_DIR_NAME = PacketBench (src-tauri/src/core/brand.rs:23).
| Platform | Log directory |
|---|---|
| Windows | %LOCALAPPDATA%\PacketBench\logs\, falling back to %APPDATA% then C:\ProgramData |
| macOS | ~/Library/Application Support/PacketBench/logs/ |
| Linux | $XDG_DATA_HOME/PacketBench/logs/, defaulting to ~/.local/share/PacketBench/logs/ |
The appender rotates daily, writing packetbench.log.<YYYY-MM-DD>
(src-tauri/src/lib.rs:24). There is no retention limit and no pruning:
the directory grows for as long as the app is used. Deleting old files is safe
and is currently the only way to bound it. Old installs may also hold a
PacketADE directory under the same parent
(LEGACY_LOG_DIR_NAME, src-tauri/src/core/brand.rs:26); nothing reads it.
Three kinds of state, and how to tell them apart#
Conflating these is the most common source of confusion, because a setting you changed in the UI may live in any of the three.
The OS keyring holds every secret#
Service packetbench. API keys, git-host tokens, SSH passwords and the Packet
Agent token, and nothing else. It is not a file, it is not in the data
directory, and it is not included in a backup of your home folder. A keyring
write failure is a hard error rather than a fallback to disk. The full entry
table is on Security & credentials.
localStorage holds UI state and two authoritative slices#
The webview's localStorage, keys prefixed packetbench:
(src/lib/brand.ts:28). It lives in the Tauri webview's profile directory,
which is managed by the OS webview runtime — WebView2 on Windows, WKWebView on
macOS, WebKitGTK on Linux — and is not a path PacketBench controls.
Most of it is presentational and losing it costs nothing: mosaic layouts, right-dock widths, sidebar preferences, the last-opened tab, onboarding dismissal, project history. Some of it is not:
| Key | Holds | Losing it |
|---|---|---|
packetbench:issues |
The authoritative cold-start cache for the issue board; every mutation is mirrored into state.v1.json so the Fixes #N close loop resolves the same set (src/stores/issueStore.ts:234) |
Issues reload from the mirror in state.v1.json. |
packetbench:mcp-hub-trust-v1 |
Per-server MCP trust profiles (src/stores/mcpTrustStore.ts:42) |
Every server reverts to the default profile: reads on, writes off. |
packetbench:provenance-audit-v1 |
The bounded trust audit, 7 or 30 days and 200 entries (src/stores/provenanceAuditStore.ts:49) |
The audit history, which is the point of the card. |
packetbench:agent-profiles |
Agent profiles and their permission modes (src/stores/profileStore.ts:12) |
Custom profiles; the built-ins are re-seeded. |
packetbench:workspaces-cache |
A paint-fast copy so Welcome renders before Rust answers | Nothing. state.v1.json is the authority. |
packetbench:issues and state.v1.json are two halves of one
record, on purpose. localStorage is the cold-start authority so the board
paints instantly; the mirror exists so Rust-side consumers see the same
issues. Clearing site data without also clearing state.v1.json leaves the
mirror as the surviving copy.
On-disk state files hold everything durable#
The table above. state.v1.json is one PersistedState struct written through
a two-tier lock so a stale full-state save cannot overwrite a slice save that
landed in between — see Architecture internals.
Outside the data directory#
These are the paths PacketBench reads or writes that are not its own.
| Path | What it is | Owned by |
|---|---|---|
<project>/.agents/memory/*.md |
Durable project notes, ordinary Markdown, committed with your repo | You and the agent. See Memory. |
<project>/.pkt-worktrees/<id> |
Git worktrees for Flight attempts and conversation branches, locally or on the remote host (src-tauri/src/commands/git.rs:385) |
PacketBench. The dirty-root check excludes it deliberately. |
<project>/AGENTS.md, <project>/CLAUDE.md |
Project rules, written as a matched pair by the Project Rules editor | You, through Settings. |
<project>/.mcp.json |
Project-scoped MCP server definitions | You or another tool. PacketBench reads it. |
<project>/.packetbench/commands/*.md |
Project-scoped slash commands (src-tauri/src/commands/slash_commands.rs:130) |
You. Read-only to PacketBench. |
~/.claude/settings.json |
Global MCP server definitions, and the Claude status-line hook | Claude Code. PacketBench reads and writes the hook. |
~/.claude/statusline-state/ |
State the PacketBench status-line helper writes for claude panes (src-tauri/src/core/claude_statusline.rs:194) |
PacketBench. |
~/.claude, ~/.codex |
Watched for CLI login changes so account badges stay accurate (src-tauri/src/commands/auth_watcher.rs:319) |
The CLIs. |
| A CLI account config directory | Any path you choose, pointed at by CLAUDE_CONFIG_DIR or CODEX_HOME |
You. PacketBench seeds settings.json and config.toml into a new one and never copies .credentials.json, credentials or auth.json (src-tauri/src/commands/cli_account.rs:29). |
$TMPDIR/packetbench-askpass-<uuid>/secret |
An SSH password, mode 0600 in a mode-0700 directory, for the duration of one ssh invocation. Unix only (src-tauri/src/core/ssh_askpass.rs:58) |
PacketBench. A Drop guard removes file and directory. |
What is safe to delete#
Three useful resets, in increasing order of loss. Close the app first.
- Reclaim disk. Delete
models/,pty-transcripts/,crashes/and the log directory. Together these are almost all of the footprint; the models alone can be several gigabytes. Nothing durable is lost. - Reset the UI without losing work. Clear the
packetbench:keys inlocalStorage. Workspaces, flights and servers survive instate.v1.json; issues reload from the mirror; MCP trust profiles revert to defaults. - Start over. Delete
~/.packetbench/entirely. This also removesssh/known_hosts, so every remote host reverts to unpinned and the next connection is trust-on-first-use. It does not clear the keyring — API keys, tokens and SSH passwords survive, and a re-created host record with a reused id will find its old password.
Deleting state.v1.json while leaving localStorage intact
produces a half-restored install: the issue board repopulates from
packetbench:issues while workspaces, flights and servers are gone. Delete
both or neither.
Where the four older tables were wrong#
Recorded rather than quietly fixed, so anyone holding a stale copy can tell which one they have.
| Source | Claim | Correction |
|---|---|---|
install.md |
known_hosts at ~/.packetbench/known_hosts |
Wrong. It is ~/.packetbench/ssh/known_hosts (src-tauri/src/core/execution.rs:12). remote.md and settings.md were right. |
install.md |
pty-active-pids/ drawn as a directory |
It is a flat file (src-tauri/src/core/pty.rs:130). |
install.md |
Tree omitted models/, ssh/, ssh-cm/, scratch/, commands/, provider-settings.v1.json, provider-launches.json, sidecar-stats.json, git-hosts.json and github-token |
All are listed above. |
install.md |
Keyring table listed six entries | Ten exist: api-key-minimax-api, api-key-ollama, git-host-token-<id> and packet-agent-token were missing. See Security & credentials. |
concepts.md |
"the app-managed known_hosts", unqualified |
Correct but unlocated; the path is now stated once, here. |
issues.md |
"Gitea connections → persisted app config" | Specifically ~/.packetbench/git-hosts.json (src-tauri/src/commands/github.rs:186). |
dev-architecture.md |
"five distinct persistence surfaces" | Accurate as a model, but it lists state.v1.json, conversations and usage.jsonl as the only files. Nine more files and six directories exist. |
| All four | No mention of the log directory | It is the one platform-specific location, and the only one that grows without bound. |
Related#
- Security & credentials — what is in the keyring, and every trust boundary these paths sit behind.
- Install & first run — first launch, and the pre-rename migration that creates this directory.
- Upgrades & migration — what happens to
~/.packetade, and what those runs proved. - Troubleshooting — symptoms that start with a missing or corrupt file here.
- Architecture internals — how
state.v1.jsonis locked, recovered and quarantined. - Memory — why project notes live in your repository rather than here.