Skip to content

Run BoB as a New Operator

This page installs BoB for a second operator: a different OS user, with an operator repo of their own that names a different forge owner. It ends with bob doctor and bob ready both passing on a sample project, and nothing of the first operator anywhere in the install. For what an operator is, see Operators.

The steps are the same ones, in the same order, that tests/acceptance/operator-clean-install.sh runs. The last section runs that script to prove the install (#2273).

Terminal only. Run every step from a plain shell, logged in as the new user. deploy.sh refuses while a Claude Code session is running on this machine — any user’s, not only yours (step 2 has the command for a shared host).

  • A user account with an empty home: no ~/.claude, no ~/projects/bigbrain, no ~/projects/bob-operator.
  • git, node (18+), jq and gh on PATH.
  • Your forge owner (a GitHub user or org) — this page uses bob-operator-scratch; put yours in its place.

1. Clone BoB and check out the newest stable tag

Section titled “1. Clone BoB and check out the newest stable tag”
Terminal window
git clone https://github.com/paulirv/bigbrain.git ~/projects/bigbrain
cd ~/projects/bigbrain
git checkout "$(git tag -l 'v*' --sort=-v:refname | awk '/^v[0-9]+\.[0-9]+\.[0-9]+$/ && !f { print; f = 1 }')"

You should see: HEAD is now at … naming a vX.Y.Z tag.

Broken looks like: fatal: could not read Username — the clone needs access to the BoB repo. Stop and paste me what you got.

Terminal window
bash ~/projects/bigbrain/scripts/deploy.sh --first-run --force

You should see: the deploy summary end without a red line, and a version file:

Terminal window
cat ~/.claude/.bob-version

shows "channel": "stable" and the tag you checked out.

Broken looks like: exit 4 (live-session). The guard lists every user’s processes, so it refuses while any Claude Code session runs on this machine. If the session is yours, quit it and run the step again. If it is another user’s (a shared host — it reads their own ~/.claude, never yours), hand the guard only your own processes, the way the acceptance script does:

Terminal window
ps -U "$(id -u)" -o pid=,command= > "${TMPDIR:-/tmp}/bob-ps-$(id -un).txt"
BOB_RECONCILE_PS_FILE="${TMPDIR:-/tmp}/bob-ps-$(id -un).txt" \
bash ~/projects/bigbrain/scripts/deploy.sh --first-run --force

BOB_RECONCILE_PS_FILE is the guard’s process-list seam; the guard counting other users’ sessions is a defect of the guard, not of your install.

3. Install the shell line, then open a new terminal

Section titled “3. Install the shell line, then open a new terminal”
Terminal window
bash ~/projects/bigbrain/scripts/bob-install.sh

This adds one guarded line to ~/.zshrc or ~/.bashrc. Open a new terminal, then:

Terminal window
command -v bob

You should see: /home/<you>/.claude/bin/bob (or /Users/<you>/… on a Mac).

Broken looks like: nothing printed — you are still in the old terminal.

Terminal window
bob operator init ~/projects/bob-operator --id scratch-tenant --label "Scratch Tenant" \
--forge-host github.com --forge-owner bob-operator-scratch --link

Then make two edits inside the operator repo — the only files you edit on this page:

  • operator.json: your people (people[].name, forge_login) and, if you want them, routes. The scaffold already names your forge owner.
  • machines.json: replace the example-laptop entry with this machine — id, hostname (what hostname -s prints), tailscale_name, role (dev-laptop, farm-worker, server or utility-host) and os (macOS or linux). Without it, bob ready cannot put the project on bob viz, because the fleet feed does not know this machine.

Commit, then check:

Terminal window
cd ~/projects/bob-operator && git add -A && git commit -m "operator: scratch-tenant"
node ~/.claude/scripts/checks/check-operator.js ~/projects/bob-operator
bob operator show

You should see: check-operator exit 0 with no problems, and bob operator show print

operator: scratch-tenant — Scratch Tenant
root: /home/<you>/projects/bob-operator (resolved via pointer)
forge: github.com/bob-operator-scratch

resolved via pointer means --link wrote ~/.claude/.bob-operator.

Broken looks like: resolved via default — the pointer was not written (--link left off). The repo still resolves, because ~/projects/bob-operator is the default place, but write the pointer so it does not depend on that. Do not re-run bob operator init … --force: it rewrites the scaffold files and loses the edits above. Write the pointer by hand instead:

Terminal window
printf '%s\n' "$HOME/projects/bob-operator" > ~/.claude/.bob-operator
bob operator show

A check-operator problem names the field; fix it in operator.json or machines.json, commit, and run the check again.

Terminal window
mkdir -p ~/projects/clean-install-sample && cd ~/projects/clean-install-sample
git init -q && git symbolic-ref HEAD refs/heads/main
printf '# clean-install-sample\n' > README.md && git add README.md && git commit -qm "initial commit"
git remote add origin https://github.com/bob-operator-scratch/clean-install-sample.git

To have BoB create the repo on your forge instead, leave out the git remote add line and add --new to bob ready in step 8 (needs gh auth login).

6. On a shared host only: move the dashboard band

Section titled “6. On a shared host only: move the dashboard band”

Skip this step on a machine nobody else runs BoB on.

Port bands come from a hash of the project name, so on a host where another user already serves the BoB fleet dashboard, yours wants the same ports. Pin the source project’s band in your ledger to a free one:

Terminal window
node ~/.claude/scripts/port-ledger.js allocate bigbrain --pin 7010 --note "shared host"
cd ~/projects/bob-operator && git add -A && git commit -m "ports: pin bigbrain"

You should see: a JSON band with "base": 7010.

Broken looks like: later, bob ready blocks at viz with already running on port … on another bind — pick another free base and run this step again.

Terminal window
bob doctor

You should see: the run end with exit 0 — converged — and an INFO operator: scratch-tenant line.

Broken looks like: a FAIL or DRIFT line. Each names its fix; run it, then run bob doctor again.

Terminal window
bob ready ~/projects/clean-install-sample

You should see: READY — clean-install-sample is warp-drive-ready on this machine. Advisories (labels not checked without gh auth login, no default automation level, no default CI locus) are fine — they never block.

Broken looks like: NOT READY with provision: … Refusing to write manifest under …/bob-operator/provisions — that is the BOB_HOME deploy target. That is a known defect of the stable release: bob ready, run from ~/.claude, hands its own directory to bob provision as BOB_SOURCE when BOB_SOURCE is not exported. Until it is fixed, run it with the source named:

Terminal window
BOB_SOURCE=~/projects/bigbrain bob ready ~/projects/clean-install-sample

9. Confirm you edited nothing outside the operator repo

Section titled “9. Confirm you edited nothing outside the operator repo”
Terminal window
git -C ~/projects/bigbrain status --short
git -C ~/projects/clean-install-sample status --short --ignored

You should see: nothing from the first command — the BoB checkout is still the tag, untouched. The second shows only what BoB writes into a project by design, all of it derived:

  • .claude/ — bob init’s links into ~/.claude and its session state
  • .gitignore — the managed ignore block that keeps those links out of git

Under the hood, bob ready also installs two git hooks (.git/hooks/pre-commit, the secret scan, and .git/hooks/commit-msg) and ignores its .logs/ in .git/info/exclude; outside the project it writes runtime state under ~/.claude and ~/.local/state/. The project’s manifest and projects.json land in ~/projects/bob-operator. None of it is an edit you made: the only files you edited by hand were operator.json, machines.json and, on a shared host, provisions/ports.json — all in the operator repo.

Broken looks like: a changed file you did not expect (M README.md, or a line in ~/.zshrc other than the one BoB block). Stop and paste me what you got.

The script does steps 1–9 for you in a fresh HOME. Its step 9 is stricter than yours: it snapshots the whole HOME before bob doctor and diffs it after bob ready, and fails on any changed path outside the operator repo that is not on its explicit list of BoB’s own writes (the ones listed in step 9). Then it adds the leak scan: it fails if anything the run printed (every report and --json document), or any file in the ~/.claude it produced, carries a literal from the operator-literal set (scripts/checks/operator-literals.json), in any case, percent-encoded or \u-escaped, in text or binary files. The one exemption is BoB’s declared upstream repo as a whole token, read from scripts/bob/upstream.json; another repo whose name only starts with it is still a leak. A file it cannot read fails the scan too.

The acceptance run — as the new user, on a host with a second user:

Terminal window
sudo -n -u bobtest -i bash /path/to/bigbrain/tests/acceptance/operator-clean-install.sh \
--expect-user bobtest --org bob-operator-scratch --evidence /tmp/2273-evidence.json

It refuses (exit 2) unless it runs as the named user with a fresh HOME. Add --dashboard-base auto on a host where another user serves the BoB dashboard (step 5), and --create-remote to let bob ready --new create the sample repo on the org.

A developer run — as yourself, in a throwaway HOME:

Terminal window
bash tests/acceptance/operator-clean-install.sh --local --source-url ~/projects/bigbrain

It records user_isolation: false, so it can never stand in for the acceptance run.

You should see: one line per step and PASS — …, exit 0. The evidence file (operator-clean-install/1) holds the tag, org, user, host, the doctor and ready verdicts, the leak list and the allow-list entries still carried until a requirement retires them.

Broken looks like: FAIL — leaks with LEAK BOB_HOME:<file>:<line> [<literal>] lines. Each is a file BoB ships that still names its original operator — a product fix, never a reason to edit your install. Stop and paste me what you got.