Skip to content

GitHub Copilot CLI for Git

Lesson 2 of 9Intermediate13 min readGitHub Copilot & AI Engineering · Copilot CLIVerified: git 2.43.0 and GitHub Copilot CLI command reference, September 2026

Git is the best fit for an agent in a terminal, for a reason worth stating precisely: its diagnostic commands are completely safe and its remedial commands are completely unsafe, with almost nothing in between.

That maps onto a permission model better than any other domain. git log should never need approval. git push --force should always need it. Getting that boundary right is most of using this well.

Before anything else, decide what the agent may run without asking.

Terminal window
copilot --allow-tool='shell(git)'

What it doesStarts a session where Git commands are pre-approved and everything else still requires approval.

Why we run itRead-only Git is safe and frequent; pre-approving it removes the approval fatigue that leads people to approve everything.

Expected resultA session that runs git commands without prompting, and still asks before other tools.

That is convenient and it is broader than it looksshell(git) includes git push --force and git reset --hard.

For a session where you want diagnosis without any risk, the safer shape is to grant nothing up front and approve individually, or to use a custom agent restricted to read-only work. The second is the durable answer for a repository where this matters.

Safe — read onlyReversibleDestructive
status, log, diff, showswitch, checkout <branch>reset --hard
blame, reflog, describestash, switch -cpush --force
branch -vv, tag --containscommitbranch -D
remote -v, config --listrevertrebase, commit --amend
ls-files, count-objectsmerge (abortable)clean -fd, filter-repo

The first column is where an agent should spend most of its time, and where its value is highest — gathering the state that makes a diagnosis possible.

The most useful low-risk application, and the one that removes the most friction.

Run git status and git branch -vv and tell me what state this repository is in and what my options are. Do not change anything.

The agent runs the commands, reads the output and explains it — which is AI Git troubleshooting with the gathering step removed. That step was most of the work, and removing it is the point of this surface.

The instruction “do not change anything” is worth including. It is not enforcement — that would be a tool restriction — but it shapes the response toward diagnosis rather than remedy, which is the order you want.

Two commands built for this.

Terminal window
{/* Review changes in the current directory */}
/diff
Terminal window
{/* Review code changes, or a specific file */}
/review
/review src/payments/client.py

The value over doing it manually is the assembly. Self-review fails because gathering the diff and framing the question is friction at the moment you most want to be finished, and these remove it.

The questions worth asking remain the same:

Review my staged changes. What did I leave in — debug output, commented code, hardcoded values? For each new code path, is there a test?

Where the agent’s ability to run commands compounds usefully.

  1. “What is unstaged that I might not want to commit?” — it runs git status and git diff and tells you what is there. This catches the accidental config change.

  2. “Stage only the changes related to the retry handling.” — it can run git add -p interactively or stage specific paths. Read what it stages.

  3. “Review the staged diff.”

  4. “Write a commit message from the staged diff.” — with the caveat from AI commit messages: the diff answers what, you answer why.

  5. “Commit.” — an approval you read.

The improvement over doing this by hand is that each step’s output feeds the next without you moving data between windows. The judgement at each step is unchanged.

Read-only, safe, and where the CLI is genuinely better than a chat window because it can iterate.

When was retry handling added to the payment client? Use git log -S, and cite the commit hashes.

The agent can run several searches, narrow the range and come back with candidates — the loop that makes history analysis fast. The citation request is what makes the answer checkable, and it matters more here because you did not see which commands it ran unless you were watching.

Worth asking explicitly on anything consequential:

Which commands did you run to reach that conclusion?

The area where care matters most, because the useful commands are the dangerous ones.

What the agent is good at: explaining a conflict, showing both sides and the base, explaining what each branch changed relative to the merge base, and identifying which files are conflicted.

What to keep for yourself: deciding the resolution. Everything from AI merge conflict resolution applies, including that the correct resolution is frequently neither side but both changes composed.

The rebase inversion catches agents too. During a rebase, HEAD is the upstream and the lower section is your commit being replayed — the opposite of a merge. Tell it which operation you are in:

This is a rebase of feature onto main. HEAD is main; the lower section is my commit.

Aborting is always available and always safe, and is the right move whenever the state has stopped making sense:

Terminal window
git rebase --abort
git merge --abort

When a session has become confusing, abort and start the diagnosis from a clean state.

The highest-value application of this surface, because reflog output is dense and the recovery commands are non-destructive.

I ran git reset --hard and lost commits. Run git reflog and tell me which entry is the state before the reset.

The agent reads the reflog and identifies the entry. The recovery is then:

Terminal window
git switch -c recovered <hash>

That command creates a branch. It destroys nothing, which makes it the ideal thing to accept from an agent — the worst case is an extra branch you delete.

This is a good illustration of the general principle: prefer remedies that create over remedies that modify. Recovering to a new branch is safe; resetting the current branch to a recovered hash is a second destructive operation on top of the first.

A common maintenance task, and a good example of read-then-act.

Terminal window
{/* Branches already merged into main */}
git branch --merged main

List branches merged into main, excluding main itself. For each, tell me the last commit date and author before I decide what to delete.

The read step matters because --merged is about reachability, not about whether the work shipped — a branch merged into main is merged; a branch whose commits were squashed onto main is not reported as merged, and deleting by the wrong criterion loses work.

Deletion is destructive. Approve them individually, or at least read the list.

The CLI can reach GitHub as well as the local repository, which extends what a Git session can answer.

Which open pull requests touch src/payments/? Show me their review status.

That combines local repository knowledge with GitHub state, and it is the kind of question that otherwise means several browser tabs.

Two routes to it:

gh, as a shell command. The agent can run GitHub CLI commands if permitted. Narrow the allowance — shell(gh) separately from shell(git) — because gh can close issues, merge pull requests and change repository settings, which is a different risk profile from reading history.

The GitHub MCP server, which exposes GitHub operations as tools with their own permission surface and, importantly, a read-only mode. For anything diagnostic, read-only is the right configuration and it removes the whole category of “it merged something”. See MCP + GitHub.

The distinction worth holding: reading GitHub state is as safe as reading Git history. Writing to GitHub is as consequential as pushing. They arrive through the same tool, so the permission has to distinguish them — which is exactly what read-only mode is for.

A place where the agent’s iteration helps and the judgement stays yours.

Splitting a messy working tree. “I have three unrelated changes here. Group them and stage the first one.” The agent can read the diff, propose a grouping, and stage by path or hunk. You read what it staged before committing — the grouping is a judgement about what belongs together, and it will sometimes get it wrong in ways that are obvious to you and not to it.

Writing several messages. After splitting, generating a message per commit from each staged diff. Same caveats as AI commit messages: the diff answers what, and the why has to come from you for each one.

Checking a branch before pushing. “Show me each commit on this branch as a separate diff. Does each one leave the tests passing? Is any of them doing two unrelated things?” That is a question about bisectability, and it is much easier to fix before the branch is pushed.

The general shape: the agent does the mechanical work of splitting and describing; you supply the judgement about what constitutes a logical change. That division holds for most Git work and is why this surface fits so well.

Force pushes. --force-with-lease over --force, always, and only after establishing whose commits are on the remote. On a shared branch this is a conversation before it is a command.

History rewriting. filter-repo, interactive rebase of pushed commits, commit --amend on anything shared. See removing secrets from history for what this actually costs.

git clean -fd. Deletes untracked files permanently. There is no reflog for files that were never committed, so this is the one Git operation with no recovery path at all.

Anything on a protected branch. If your rulesets are correct this fails anyway, which is the system working — and it is worth noticing that the failure came from policy rather than from the agent declining, because policy is the layer that holds regardless of who or what is pushing.

Credential configuration. Changes to credential.helper or remote URLs affect where your credentials are stored and which host they are sent to. A remote URL rewritten to point somewhere else is a credential disclosure with a plausible explanation attached. See Git credentials.

The durable answer to “I want diagnosis without risk”, and a natural first custom agent.

.github/agents/git-doctor.md
---
name: git-doctor
description: Diagnoses Git repository state and explains options. Read-only —
never changes repository state.
tools: ["read", "search", "shell"]
---
You diagnose Git problems. You explain state and options; you do not change
anything.
Rules:
- Only run read-only Git commands: status, log, diff, show, blame, reflog,
branch, tag, remote -v, describe, ls-files, count-objects.
- Never run: reset, rebase, merge, checkout, switch, push, commit, clean,
branch -d/-D, tag -d, stash, filter-repo, or anything with --force.
- Always report which commands you ran and their relevant output.
- When proposing a remedy, describe what it would do and what would be lost.
Do not run it.
- Prefer remedies that create — a new branch — over ones that modify.

Two honest caveats about what that buys.

The instructions are a request, not enforcement. An agent restricted this way is much less likely to run a destructive command and is not prevented from it. For prevention, the mechanisms are the tool list itself and a preToolUse hook that denies commands matching a pattern.

A narrow tools list is the stronger control. Where the agent format lets you restrict tools rather than just describe restraint, that is the line doing the work.

Even so, the agent is worth having. It changes the default from “an agent that could do anything” to “one whose whole framing is diagnosis”, and in practice that changes what it proposes — which is most of the benefit for a task where you were going to read the commands anyway.

Diagnosing and fixing a messy branch.

  1. Start with Git allowed, nothing else: copilot --allow-tool='shell(git)'.

  2. Ask for state, explicitly read-only. “Run status, log --oneline -10 and branch -vv. What state am I in? Do not change anything.”

  3. Check the diagnosis against the output yourself. It ran the commands; the output is in your terminal.

  4. Take a backup branch. git branch backup/before-fix. One command, and everything after is reversible.

  5. Ask for options with consequences. “What are my options, and what does each one lose?”

  6. Pick, and confirm you can state the effect before approving.

  7. Verify. Status and log again. Did the result match the prediction?

Step 4 is the one that makes the rest low-stakes, and it is one command.

Being honest about the trade, because Git is a tool most engineers are already fast with.

It wins clearly on: anything where the answer requires several commands whose output feeds the next. Diagnosis, archaeology, working out what happened. Typing four commands and reading their output is slower than asking one question, and the agent does not mistype a hash.

It wins moderately on: commit preparation and self-review, mostly by removing friction rather than by being better. The friction was the reason people skipped the step, so removing it changes behaviour.

It does not win on: commands you already know. git status is faster to type than to ask for. An agent adds latency to anything you would have done reflexively.

It loses on: anything destructive, where the extra step of reading and approving a command you did not write is slower than writing the command yourself — and the approving is not optional.

The productive position: use it where the value is synthesis, and type the commands you already know. An agent that runs git status on request is a slower git status; one that reads the reflog and tells you which entry preceded the reset is doing something you would have spent five minutes on.

--allow-tool='shell(git)' treated as safe. It includes reset --hard and push --force.

Approving a destructive command because the explanation was fluent. Same system, same confidence.

Not taking a backup branch. One command, and the difference between a mistake and an incident.

Letting it decide a merge resolution. It cannot verify behaviour; tests can.

Not saying whether you are in a merge or a rebase. The conflict text is ambiguous and the default assumption is merge.

Deleting branches by --merged alone. Squash-merged branches do not report as merged.

Not asking which commands it ran. For a conclusion you will act on, that is the evidence.

Using it to fix something you do not understand. The situation where you can least evaluate a command is the one where approving it matters most.

A few repository instructions change agent Git behaviour usefully, and they are worth having wherever this surface is used regularly.

## Git
- Commit messages follow Conventional Commits. Valid scopes: api, web, worker,
infra, deps.
- Never propose `git push --force`. Use `--force-with-lease`, and only after
confirming the remote state.
- Before any command that rewrites history, state what would be lost and
suggest a backup branch first.
- Do not commit without showing me the staged diff.
- Never run `git clean`.

Each line addresses a specific failure from this article. The third is the most valuable — it makes the agent produce the safety framing without being asked, which is the framing you want present at exactly the moment you are least likely to ask for it.

The caveat, as always: these shape rather than enforce. The line about git clean reduces the chance of it being proposed; a preToolUse hook matching git clean and denying it is what makes it impossible.

Where a team uses this surface heavily, the combination is worth building: instructions for the behaviour you want, a hook for the handful of commands that must never run, and a ruleset for the branch-level protections that apply to everyone regardless of tooling.

Git’s commands divide cleanly into ones that only look and ones that can lose work. An agent should live in the first group by default and cross into the second only with an approval you actually read.

  • Git’s read-only commands are safe to pre-approve; its remedial ones are not
  • --allow-tool='shell(git)' includes the destructive commands
  • The agent’s real value is removing the gathering step from diagnosis
  • /review and /diff make self-review low enough friction to actually happen
  • Ask which commands it ran when you will act on a conclusion
  • Tell it whether you are in a merge or a rebase; the conflict text does not say
  • Prefer remedies that create — git switch -c recovered — over remedies that modify
  • --merged reports reachability, so squash-merged branches are not listed
  • A backup branch costs one command and makes everything after reversible

Use a disposable repository.

  1. Start with --allow-tool='shell(git)'. Ask it to explain the repository state. Predict: does it ask permission for git log?

  2. Ask it to run git reset --hard HEAD~1. Predict: does the allowance cover it?

  3. Restart with no allowances. Repeat step 1. Predict: how many approvals?

  4. Create a mess: commit on the wrong branch, then reset it away. Ask the agent to recover it using the reflog. Predict: does it propose a command that creates, or one that modifies?

  5. Create a merge conflict. Ask for an explanation without saying whether it is a merge or a rebase. Then say which. Compare.

  6. Ask it to list branches merged into main. Squash-merge a branch and ask again. Predict: is the squashed branch listed?

  7. Delete the repository.

AI-assisted engineering learning pathEleven lessons on getting value from Copilot and agents without giving up review discipline.