Documentation
Git
Typed git tools, and the one operation Forge will not perform.
Forge treats git as a first-class tool rather than a string it types into a shell. Each operation has a typed schema and shell-quoted arguments, so a branch name with a space in it is a bad branch name and not an injected command.
Forge never pushes
git push is on the deny list. Not gated, not escalated - refused, in every approval mode including yolo, with no flag to turn it off.
This is the single most important line on the page. Whatever the agent does, it does locally. Nothing reaches a remote, a CI pipeline, or a colleague until you push it yourself. Reviewing a diff is optional; that is your call. Publishing is not the agent's to make.
The same applies to anything that rewrites shared history or reconfigures where a remote points. git remote, git rebase, git filter-branch, and tag or branch deletion are all escalated to destructive so they stop for a human even when the rest of the run is unattended.
Why typed tools
- Arguments are quoted - the model supplies fields, not a command line. There is no string for it to get creative with.
- Risk is per operation -
git_statusandgit_resetare not the same call with different words. One is a read; the other can destroy uncommitted work, and the policy sees that. - Output is structured - a diff comes back as a diff. The model reads its own changes before committing them.
Reading the repository
git_status, git_diff, git_log, and git_show are all reads, so they run unattended in every mode. This is most of what the git tools are used for: orienting before a change, and checking its own work afterwards.
Uncommitted state at the start of a run is context, not an obstacle - Forge sees what you were in the middle of.
Changing the repository
git_add, git_commit, git_branch, and git_checkout are writes. In the default auto mode they run without asking; in cautious each one stops for you.
› commit the rate limiter work on a new branch ◉ git_status 3 modified, 1 untracked◉ git_diff◉ git_branch create: feat/rate-limit◉ git_add middleware/rate_limit.py app/config.py tests/◉ git_commit ✓ Committed 4 files to feat/rate-limitgit_commitcommits only what is staged, unless it is given explicit paths or asked for all tracked files.git_branchlists or creates. Deletion is gated separately.git_checkoutswitches refs freely, but restoring paths from a ref discards uncommitted changes and therefore needs approval.
The two destructive ones
git_revert and git_reset are marked destructive, so they reach you in cautious and auto alike.
git_revertadds a commit that undoes an earlier one. Nothing is lost, but history gains an entry you may not want.git_resetmovesHEAD. Inhardmode it permanently discards uncommitted work - the one git operation with no undo. Read the prompt before approving it.
A workflow that works
Commit before you start
A clean tree is the best safety net there is. With your work committed, git diff shows exactly what the agent did, and git checkout . undoes all of it.
$ git add -A && git commit -m "wip"$ forge "extract the retry logic into a decorator"$ git diffGive it its own branch
Ask for a branch in the task itself, or start on one. Either way the agent's work stays separable from yours.
$ git switch -c forge/retry-decorator$ forge "extract the retry logic into a decorator"Turn them off if you would rather
The git tools are on by default. Set FORGE_ENABLE_GIT_TOOLS=false and the agent edits files while leaving version control entirely to you.
$ FORGE_ENABLE_GIT_TOOLS=false forge "fix the failing auth test"- Tools - every git tool with its risk level.
- Permissions - the deny list and how escalation works.