Files and State
Where sand keeps its config, state, and per-VM files — on the host and in
the guest. This is the only page in these docs that spells these paths
out; every other page links here instead of restating them.
Host paths
| Path | What it holds | Safe to delete? |
|---|---|---|
${XDG_CONFIG_HOME:-~/.config}/sandbar/profiles.yaml |
The Connection Profiles config: every location sand can run VMs on (the permanent Local profile, plus any remote-ssh or proxmox profiles you've added), each profile's connection settings, and which profile was last used. Secret-free — no password or key material, only a path to a key file — and hand-editable YAML. |
Yes, but you lose every remote profile's connection settings (host/user/port/etc.) — sand reseeds a fresh Local-only file on next start. The VMs and their disks on any remote host are untouched; you'd need to re-add a profile pointing at that host to manage them again. |
${XDG_DATA_HOME:-~/.local/share}/sandbar/managed-vms.json |
The secret-free index of every sand-managed VM and golden template. Schema version 4 adds scoped template records and each VM's optional templateSource provenance to the schema-v3 (connection scope, name) model. |
Yes, but you lose reset defaults, template metadata, and sand's record of which VMs it manages. VM and template disks on their providers are untouched. |
${XDG_DATA_HOME:-~/.local/share}/sandbar/secrets.json |
The secret store: every KEY=VALUE pair you've set for every VM, across all scopes. Stored unencrypted, mode 0600 inside a 0700 directory. |
Only if you're prepared to re-enter every secret for every VM — deleting it does not touch a running VM's already-rendered guest files, but the next apply will have nothing to render. |
${XDG_DATA_HOME:-~/.local/share}/sandbar/managed-vms.json.lock, …/secrets.json.lock |
Empty files used to coordinate writes to the managed index and secrets store. Before changing either store, sand locks it and re-reads its current contents so concurrent processes keep each other's updates. If the lock is unavailable or the wait times out, the write proceeds without it. |
Delete only when no sand process is running. They contain no data and are re-created when needed, but deleting an active lock can allow conflicting writes. |
${XDG_STATE_HOME:-~/.local/state}/sandbar/onboarding.json |
Whether onboarding has been acknowledged ({"acknowledged":true}). Sand writes it with mode 0600 in a 0700 directory. |
Yes. The screen appears again the next time sand starts. VM, profile, and secret data are unaffected. |
${XDG_CACHE_HOME:-~/.cache}/sandbar/release-check.json |
Disposable release-check metadata: the last attempt time and last validated stable release/tag URL. Created with mode 0600 in a 0700 directory. |
Yes. Sand can check GitHub again on its next startup; no VM or configuration data is affected. |
${XDG_CACHE_HOME:-~/.cache}/sandbar/release-check.json.lock |
Advisory lock used to reserve a due release-check attempt before networking and update the shared cache safely when multiple sand processes run. It prevents duplicate daily requests and stale writes. | Delete only when no sand process is running. It contains no user data and is re-created as needed; removing an active lock can allow duplicate checks or conflicting cache updates. |
${LIMA_HOME:-~/.lima} |
Lima's own instance store — disk images, lima.yaml, per-instance logs. sand does not own this directory; Lima does. |
sand never deletes it directly; use limactl delete or Lima's own tooling. |
${XDG_STATE_HOME:-~/.local/state}/sandbar/proxmox/<host>-<node>-<pool>/ |
For a Proxmox profile only: sand's local state about that endpoint's VMs (the base-version stamp and its lock). Proxmox itself owns the VMs — this holds only what has no home on the Proxmox host. One directory per host/node/pool. |
Yes — sand re-stamps the base on the next build. The VMs on the Proxmox host are untouched. |
Proxmox API token file (path you chose, e.g. ~/.config/sandbar/pve1.token) |
The user@realm!tokenid=value credential a Proxmox profile's token_file points at. Not managed by sand and never written by it — you create it (see Proxmox VE). Must be mode 0600; sand refuses to read a looser one. |
Only if you're prepared to re-issue the token — it's the sole secret that grants access to the pool. |
${XDG_CONFIG_HOME:-~/.config}/sandbar/drupalorg.token |
Your drupal.org account personal access token, read only on the host for publishing a guest checkout's commits to drupal.org. Not managed by sand and never written by it — you create it, with the token as its only content. Must be mode 0600; sand refuses to read a looser one, and refuses to publish at all if the file is absent. The path is a fixed convention, not a configured value: there is deliberately no profiles.yaml key and no form field for it, because the credential is workstation-global rather than per-location. It is deliberately not in the secrets store either — that store exists to deliver secrets into a guest, and no drupal.org credential ever enters a VM (see Security Model). |
Only if you're prepared to re-issue the token. Deleting it disables publishing until you recreate it; nothing else is affected. |
Sources: internal/profiles/store.go (profiles.yaml default path, schema, and CRUD), internal/registry/registry.go:53 and internal/registry/registry.go:62 (managed-vms.json default path), internal/registry/registry.go:172 (the config saved into that index is the create config, stripped of the clone token), internal/registry/registry.go:107-internal/registry/registry.go:129 (the v3 (scope, name)-keyed array shape), internal/secrets/secrets.go:124 and internal/secrets/secrets.go:133 (secrets.json default path), internal/secrets/secrets.go:342-internal/secrets/secrets.go:350 (directory forced to 0700) and internal/secrets/secrets.go:357-internal/secrets/secrets.go:359 (file created at 0600), internal/provision/baseversion.go:62-internal/provision/baseversion.go:68 (LIMA_HOME fallback to ~/.lima), internal/drupalorg/token.go (TokenPath's XDG_CONFIG_HOME fallback and doc comment, and LoadToken's mode check).
Golden template instances
Each template is stored by its provider under the reserved name
sandbar-tmpl-<name>. For Lima that is an instance directly under
${LIMA_HOME}; Proxmox stores a real PVE template. Do not delete the backing
instance directly: use sand template delete <name> so the scoped registry is
updated as well. Template playbook-version metadata is used to report
current, stale, or unknown status.
Two different "scope" dimensions
Two unrelated things are both called a "scope" in this file store, and it's easy to conflate them:
- The connection scope (
registry.Scope: which Connection Profile — local, a specificuser@host:portfor remote-Lima, or ahost:node/poolfor Proxmox — a VM lives on) is what schema version 3 added to bothmanaged-vms.jsonandsecrets.json. It's what lets the same VM name exist independently on two different profiles. - The secrets store's pre-existing directory scope (
global, or a path likefoo/bar) is unrelated and unchanged by this — it's within one VM, picking which guest directory a secret's.envlands in. See Secrets.
A single secret is addressed by the combination of both: connection scope → VM name → directory scope → key.
Legacy data-directory migration
If you installed an older version of this tool under its previous name,
claude-code-ansible, its VM index lived at
${XDG_DATA_HOME:-~/.local/share}/claude-code-ansible/managed-vms.json. The
first time sand starts and finds no sandbar/managed-vms.json yet, it
copies that legacy file into the new location — copy-then-verify-then-remove,
so a crash mid-migration cannot lose it — and removes the old directory if it
ends up empty. Your VMs are not lost; they are simply indexed under the
new path from then on. This runs at most once: if sandbar/managed-vms.json
already exists, the migration is skipped.
Source: internal/registry/registry.go:68-internal/registry/registry.go:90 (migrateLegacyIndex), called from Load at internal/registry/registry.go:95.
Coding-agent preferences and migration
${XDG_DATA_HOME:-~/.local/share}/sandbar/agent-preferences.json stores the
last submitted agent selections in a version 1, secret-free JSON file.
It is global across connection profiles. Explicit saved choices, including
all four agents off, override the initial all-off selection. Deleting it
forgets those preferences without changing existing VMs.
If that file is absent, an older version 2 base stamp can seed Claude Code
and Codex preferences once. Modern version 3 base stamps record shared
dependencies rather than agent selections. Existing VM records keep their
Claude Code and Codex boolean values, including explicit false; absent
OpenCode and Pi selections mean off. Reset uses the VM's recorded selection,
not whichever agents were chosen most recently for a different VM.
Preserved agent state
The reset form's single Preserve agent settings and files checkbox preserves these paths relative to the guest user's home:
| Agent | Paths |
|---|---|
| Claude Code | ~/.claude, ~/.claude.json, ~/.config/sandbar/claude-remote-control-onboarding-complete |
| Codex | ~/.codex, ~/.config/sandbar/codex-remote-control-onboarding-complete |
| OpenCode | ~/.config/opencode, ~/.local/share/opencode, ~/.local/state/opencode |
| Pi | ~/.pi/agent |
Missing paths are skipped. Custom state locations outside this list are not
covered. Directories named .cache are excluded consistently with every other
reset preserve mode. These paths can contain credentials, session history, and
extensions; preservation includes all four agents regardless of
the VM's recorded selections, including manual installs. The data passes through a private
host temporary directory during reset. See Security Model
before preserving state from a VM you do not trust.
The Sandbar Claude and Codex markers remember whether their respective remote
control onboarding was accepted or declined. Claude's choice itself lives in
the preserved ~/.claude/settings.json. When Codex onboarding was accepted,
provisioning reinstalls and starts the user app-server service using the
preserved Codex daemon settings.
Source: internal/provision/preserve.go (AgentStatePaths).
Guest paths
| Path | What it holds |
|---|---|
~/.config/sandbar/secrets.env |
The VM's global-scope secrets, one export KEY='VALUE' line per pair. Sourced from both ~/.profile and ~/.bashrc so it's available in every shell type. Removed on VM start if no global secrets are stored, so stale values don't linger. |
~/<scope>/.env |
A scoped secret set for directory <scope>, auto-loaded by direnv when you cd into it and unloaded when you leave. |
Source: internal/provision/secrets.go:37 (global scope sourced from .profile/.bashrc) and internal/provision/secrets.go:78 (RenderDotenv for a scoped ~/<scope>/.env).
See Security Model for why the host secrets store is unencrypted and what that means for you.