Skip to content

Stop Committing Provisioned Links

BoB links provisioned skills, commands and agents into a project’s .claude/. Those links are derived: they are gitignored and rebuilt on each machine. A repo provisioned before that rule still commits its links. Every bob ready or bob provision refresh then retargets or prunes them for this machine, which leaves git dirt (M and D under .claude/) after every run. bob provision untrack is the one-time migration that fixes this.

Terminal only. It rewrites the git index and records a commit.

  • Stop any warp-drive run in the repo. untrack refuses a repo with an active warp-drive state file.
  • Commit or stash your own work. untrack refuses a staged index or a tracked change outside .claude/, and names the reason.
  • You do not need to commit the port settings that bob init wrote. Those are the _bob_ports key, server.port and the port in server.command and access.localhost in dev.json, and the bob:ports block in vite.config.* or wrangler.toml. untrack recognises them and does not refuse the repo. The report lists each such file on a ports line, and the untrack commit includes it. A file with any other edit besides that is still refused.
Terminal window
cd <project>
bob provision untrack

It prints one line per repo and a summary. The summary looks like 1 repo(s): 0 link(s) to untrack; 1 clean, 0 skipped, 0 refused, 0 error — report only, re-run with --apply. It exits 0 when the repo is already clean and 1 when there are links to untrack. The report changes nothing.

It selects only symlinks (git mode 120000) under .claude/ whose target resolves into BOB_HOME or the BoB registry, or to nothing on this machine. It never selects a real file. It keeps, and reports, a symlink that points anywhere else. A repo already reported clean needs nothing.

It also untracks only a path the managed ignore block covers (#2536). Untracking a link the block will not ignore would turn a committed link into permanent ?? dirt — the mess this migration removes. The covered set is not guessed: it comes from the block’s one writer, which you can read yourself:

Terminal window
bob provision ignore-paths # the paths the block covers here
bob provision ignore-paths --json # bob-ignore-paths/1

A BoB-owned link outside it is reported uncovered, left tracked, and named:

uncovered /Users/me/projects/iwpi-lab — 0 link(s)
7 BoB link(s) the managed ignore block does not cover
(.claude/journal/templates/decision.md, …) — left tracked:
untracking them would leave permanent ?? dirt. Fix: declare the
location in provision.js updateIgnoreBlock, or move the link
under a covered .claude/ directory (#2536)
uncovered .claude/journal/templates/decision.md -> ~/.claude/templates/journal/decision.md

That is drift (exit 1), not a failure: the covered links in the same repo are still untracked in the same run. Legacy link locations seen so far are .claude/journal/templates/*.md and .claude/templates/journal. If the engine cannot read the covered set at all, the repo is skipped and nothing is untracked — coverage is never assumed.

Terminal window
bob provision untrack --apply # untrack, ignore, one commit on the current branch
bob provision untrack --apply --push # …and push it

The links stay on disk. Only the index changes. The managed ignore block in .gitignore gains each path, and one commit is recorded: chore: untrack provisioned .claude links (#1479).

Terminal window
bob provision untrack --fleet # report for every active manifest's checkout here
bob provision untrack --fleet --apply # one commit per repo

Run it once on each machine. The farm cannot reach the laptops. A repo whose manifest sets _meta.track_provisioned: true is skipped on purpose.

Terminal window
bob provision untrack; echo "exit=$?" # 0: clean — a second run is a no-op
bob provision refresh && git status --short .claude/ # prints nothing

Exit 1 after --apply means something is still left over, and the report says which kind (#2536):

Report What it means What to do
dangling link(s) the link’s target is gone bob provision prune --apply, then check again
live link(s) still visible to git status the managed ignore block does not cover them — prune will not touch a live link declare the location in the block’s writer (provision.js updateIgnoreBlock), or move the link under a covered .claude/ directory
uncovered the same cause, caught before anything was untracked as above; nothing was left dirty

Exit 3 means the repo was refused, and the report says why.