Skip to content

Project files

Each synced folder carries its own settings, so configuration travels with the project.

The folder’s settings directory. config.json holds the stable mount id plus the project and remote (older mounts may also carry a legacy include list — still honored, never written now).

.bdrive/config.json
{ "id": "m-5a10b713", "volume": "notes",
"remote": "https://drive.example.com/p/7f3a2c91-4d5e-4b8a-9c17-2ad0f6b3e9c4" }

Written by bdrive init and safe to hand-edit — a running daemon picks changes up automatically. Which subfolders sync is not stored here: that lives in .bdriveignore (see below), edited with bdrive scope add/rm. Mounts created before that change may still carry an include list here; it is still honored, but nothing writes it any more.

It is never synced and holds no credentials; the session token stays in ~/.bdrive.

Because everything is keyed by the mount id, the folder can be renamed or moved freely. Copy it to another machine and bdrive init resumes the same project.

An optional shell command run on this device after a sync applies changes from the hub — the event a local search index, cache or notifier can hang off instead of polling.

.bdrive/config.json
{ "id": "m-5a10b713", "volume": "notes",
"remote": "https://drive.example.com/p/7f3a2c91-…",
"post_sync": "qmd update && qmd embed" }

The applied batch arrives as JSON on stdin, with the command’s working directory set to the folder:

{ "project": "m-5a10b713", "folder": "/Users/you/notes",
"changed": [ { "path": "wiki/onboarding.md", "op": "write" },
{ "path": "notes/retired.md", "op": "delete" } ] }
  • Once per cycle that applied at least one path — an initial sync of 400 files is one invocation, not 400.
  • Inbound only. A cycle that only commits and pushes your own edits fires nothing. (bdrive restore does count: it writes an older version back into the folder.)
  • Never blocks sync. The command is spawned detached; one that hangs, exits non-zero or does not exist is logged to daemon.log and forgotten.
  • Off unless set. No post_sync key, no new behavior.

This is local configuration and nothing else can set it: .bdrive/ never syncs, so no hub response and no teammate’s change can put a command on your machine. The one path in is your own hands — .bdrive/ travels with a folder, so a folder you copy from a teammate brings their post_sync along with it.

A gitignore-style opt-out list at the mount root. It syncs like a normal file, so every device shares the same rules. See Scoping the folder.

Some paths are excluded in both directions — never scanned, never uploaded, never written onto a teammate’s disk — regardless of .bdriveignore:

Path Why
.bdrive/ The mount’s own identity. Syncing it would let one device repoint another.
.git/ Carries hook scripts that would run on a teammate’s next commit.
.claude/settings.json, .claude/settings.local.json, .codex/hooks.json, .codex/config.toml, .gemini/settings.json, .hermes/config.yaml Agent hook configuration is a shell command a teammate would be installing on your machine. BearDrive’s own hooks go in each machine’s user config instead.
.mcp.json Project-scoped MCP servers: command + args pairs your agent LAUNCHES when a session starts in the folder. Same reason as the hooks above.
.DS_Store, .bdrive-tmp-* Noise and in-flight temp files.

Everything else under an agent-config directory — .claude/skills, .claude/commands, .claude/agents, AGENTS.md, CLAUDE.md — syncs normally. Sharing what an agent reads is the product; sharing what it runs is not. See What agents read.

.bdrive/workspace.json — a workspace root

Section titled “.bdrive/workspace.json — a workspace root”

A folder that holds projects, rather than being one, can carry a manifest naming which of its immediate children are project folders — one this device has paused, or has never connected, is listed too: the manifest answers “which of these are BearDrive projects”, not “which are syncing right now”. The Mac app writes one at the folder you connect; the CLI never creates one on its own.

// <root>/.bdrive/workspace.json
{ "kind": "workspace",
"projects": [ { "path": "team", "id": "m-5a10b713" } ] }

A root is not a project: nothing in it syncs — not the manifest, not the folders beside your projects — and bdrive init refuses to mount one, telling you to run it in a folder underneath instead. A root is never a project folder itself. (Roots are not meant to nest either, but connecting two folders where one is inside the other will produce that — harmlessly: the inner folder simply ends up with two indexes, and an index decides nothing.)

The file is only an index — identity lives in each project’s own config.json — so a wrong or stale entry is harmless: every daemon start rebuilds the list from what is actually on disk, and a hand-edited entry is corrected rather than obeyed. Deleting the file is different: it is how you un-root the folder. Nothing recreates it, and bdrive init will then treat the folder like any other. A corrupted manifest has the same effect.

Run bdrive stop in the projects under it first, though: a re-index that is already running can write the file back one last time, because it decided the folder was a root before you deleted it.

Inside a project, those two are all BearDrive puts there: .bdrive/config.json, .bdriveignore, and your own files. (A project created from a template also starts with an AGENTS.md and a directory skeleton — but those are ordinary synced files, yours to edit or delete like any other; nothing reads them but your agents.) No agent-config directory is ever created here — the sync hooks live in each platform’s user config (~/.claude/settings.json, ~/.codex/hooks.json, ~/.gemini/settings.json, ~/.hermes/config.yaml), written once per machine. See Hooks in detail.

Everything else lives under $BDRIVE_HOME (default ~/.bdrive):

Path Contents
device.json This device’s identity, used in change tracking
settings.json Default server, device token, signed-in account
mounts.json Mount registry, keyed by stable mount id, holding each mount’s last-known path
volumes/<mount-id>/ The local volume store: blobs, journals, materialization cache, sync state

Nothing is keyed by folder path, which is why moves and renames are free. ResolveMount self-heals the registry path on the next command.

~/.bdrive/volumes/<mount-id>/
├─ blobs/ content-addressed file content (sha256)
├─ journal/ one append-only op log per device
├─ state.json what's materialized
├─ sync.json lamport clock + push cursor
└─ secrets-<mount-id>.json files that looked like they held credentials when
they last changed (what `bdrive status` reports)

Also here for a running project: daemon.pid and daemon.log.

The hub’s storage adds two key classes the local store never holds: files larger than 4 MiB travel as content-defined chunks/<sha256> pieces plus a manifests/<sha256> chunk list keyed by the whole file’s hash (delta sync — a small edit to a large file uploads roughly one chunk, not the file). Local blobs stay whole; chunking exists only on the wire and in the hub’s store, and the hub reassembles a whole blob on demand for any client that asks for blobs/<sha256>, so older clients keep working unchanged.