development- the branch Capy offers by default the first time you runcapyin a project. Full read/write for every member.staging- a separate set of values, possibly with different API endpoints.production- protected, invite-only, stricter access.
PK - but each branch has its own encrypted blob on the service and its own pins in keep.lock. Access to a protected branch is gated by the service: check out a branch you haven’t been granted and the fetch fails, so Capy tells you the branch is invite-only and to ask a project admin for access instead of handing over the ciphertext.
Capy branches are independent of git branches. They’re tracked separately. That said, when you switch git branches, capy will let you know if your secrets are up to date for the particular version of code you are looking at.
Git branches vs. Capy branches
You typically have many git branches and a handful of Capy branches. Feature and bugfix branches all keep working against whichever Capy branch the directory is on - usuallydevelopment. When you need staging or production values, you switch explicitly with capy checkout.
.env header (# capy:branch=…) and in .capy/branch, and it gitignores both. So switching git branches leaves you on the same Capy branch - only capy checkout, or picking a branch from capy branch, changes it. Every working copy keeps its own record, which means a second clone or a git worktree can sit on a different Capy branch than this one.
What git does carry is keep.lock - a small, committed manifest file at the project root. One file holds every branch’s metadata, so whichever git branch you’re on, Capy knows what each Capy branch is supposed to contain.
What keep.lock contains
- Org ID, project ID, and project name - which Capy project this directory belongs to.
- Schema version - for format evolution.
- Variable manifest - every secret name, its resource ID, and a hash per Capy branch. Hashes, not plaintext, not ciphertext, not keys.
keep.lock. It’s just the list of what exists, enough for Capy to pull the right ciphertext from the service and diff against .env. Safe to commit, because everything sensitive stays on the service side and in your ignored .env.
Why commit it
Becausekeep.lock is committed, it travels with the repo. A teammate who clones it gets the same project, the same variable list, and the same per-branch hashes, so capy can tell right away whether their .env matches what a branch pins. Capy commits keep.lock for you whenever a sync or a push changes it - set CAPY_NO_AUTOCOMMIT=1 to opt out.
A fresh clone has no .env and no .capy/, so no branch is recorded locally yet. On the first capy run there, Capy asks the service for the project’s branches: if there’s only one, it uses it; otherwise it asks which branch you want, never preselecting a protected one.
Commands
checkout, Capy rewrites .env in place with the snippets from the new branch. It refuses to switch while the branch you’re on has uncommitted changes (.env differs from what keep.lock pins) or unpushed changes (keep.lock differs from your last sync) - run capy or capy push first. Both checks are skipped when you pass -b.
Both commands need the Capy service, so both are disabled in local-only mode.
Typical workflow
keep.lock, and - for protected branches - its own access list.
What’s next
Protected branches
Invite-only branches for production.
capy branch
Full command reference.