Never mutate git

Every git invocation Shepherd's own CLI makes is read-only: status, rev-parse, log, diff, remote get-url, for-each-ref. It never runs add, commit, apply, checkout --, reset, rebase, merge, push, fetch, pull, stash, restore, switch, cherry-pick, revert, am, or config --set.

This isn't caution for its own sake. It's a specific, previously-learned failure mode:

Mutating git subprocesses can leave .git/index.lock behind on hook failure, breaking every subsequent operation in the worktree until cleaned up by hand. The calling agent already owns the git lifecycle and has the retry / cleanup / sandbox-bypass machinery to handle this correctly.

A tool that shells out to git commit on your behalf either owns that lock file's failure modes too, or it doesn't and you find out at the worst time. Shepherd sidesteps the whole class of problem by never taking the lock in the first place.

Emit, don't execute

Where a git or GitHub mutation is genuinely the next step, Shepherd builds the exact command and hands it back instead of running it:

In every case, the output is inert until the calling agent — or you — decides to run it.

Why this belongs on the agent

The agent already has retry logic, sandbox-bypass handling, and cleanup for its own git operations, because it has to: it's the thing actually managing the working tree across a whole session, not just one tool call. Duplicating that machinery inside Shepherd would mean two systems with opinions about the same lock file. Keeping git mutation entirely on one side of that boundary is what makes the boundary safe to reason about.