Runbook — Locking shared writable state (.env, git worktrees) under /opt¶
Use when: you are about to edit a shared
.envfile, or run a multi-step git transaction (add+commit+push) on a repo working tree under/optthat more than one Claude Code session can touch — i.e. anything outside a per-sessiongit worktree.
Why this exists¶
Multiple Claude Code sessions can run concurrently against the same VPS, and
several /opt/dev/* and /opt/*-compose/ trees are shared, non-worktree
working directories with one .git/.env between all sessions.
A single git invocation locks its own .git/index.lock, but a sequence of
invocations is not atomic. On 2026-07-07 two sessions hit /opt/dev/jb-brain
at the same time: origin/main moved underneath one session mid-commit, a
sibling git reset cleared the other session's staged index, and both wrote
.gitignore over each other. It converged by luck that time — no lost commit,
no leaked secret — but the same race can just as easily land a bad commit or a
secret in history. The same day, #937 and #1158 raced on a shared .env.
Task #1175 tracked the fix. See [[feedback_shared_git_worktree_race]] in
memory.
The fix: with-lock.sh¶
/opt/infrastructure/scripts/with-lock.sh holds an exclusive flock for the
entire wrapped command, keyed by a hash of the resource path. Any two
sessions naming the same resource path serialize against each other
automatically — no shared session state needed beyond the filesystem.
resource-path— any stable identifier for the thing being protected. Use the repo root for git operations, the file path for.envedits. It does not need to exist; it's only hashed to pick a lock file.--timeout— seconds to wait for the lock before giving up (default 60).- Exit codes:
0success (wrapped command's own exit code is returned),124lock not acquired within timeout (resource busy — do not retry in a loop, back off and tell the user),2usage error.
Wrapping a git transaction¶
with-lock.sh /opt/dev/jb-brain -- bash -c '
cd /opt/dev/jb-brain &&
git add -A &&
git commit -m "fix: ..." &&
git push origin main
'
Wrapping a .env edit¶
with-lock.sh /opt/rabbithall-compose/.env -- bash -c '
cp /opt/rabbithall-compose/.env /opt/rabbithall-compose/.env.bak-$(date -u +%Y%m%d%H%M%S) &&
sed -i "s/^SOME_KEY=.*/SOME_KEY=newvalue/" /opt/rabbithall-compose/.env
'
When you can skip this¶
- Any repo already checked out into a dedicated git worktree for your
session (
git worktree add) — no other session shares that working tree. - Read-only operations (
git status,git log,cat .env).
Troubleshooting¶
Lock times out (exit 124)¶
Another session is mid-transaction on the same resource. Do not force past it
(rm the lock file, --timeout 0, etc.) — that reintroduces the exact race
this exists to prevent. Wait and retry, or tell the user the resource is busy.
Stale lock after a crashed session¶
flock releases automatically when the holding process exits or dies — there
is no cleanup step needed. The /opt/infrastructure/locks/*.lock files are
harmless to leave in place; they are re-used by path hash, not recreated.
Not yet covered¶
This wrapper is opt-in per invocation — it does not intercept bare git/vi
calls that bypass it. Enforcing it automatically (e.g. via a git wrapper
alias on PATH) was considered and rejected for now: it would silently change
behavior for tooling that doesn't expect to be wrapped. Current mitigation is
this runbook + the ticket note on #1175 — every session working on a shared
/opt tree or .env is expected to use it.