2026-08-25 · Tutorial

Grok Bot and GitHub: Permissions and What to Automate

A bot with write access to your default branch is not a more capable version of a bot that leaves comments. It is a different category of thing, with a different failure mode, and it should be evaluated as such.

Every other integration in this series fails loudly and locally. A bad Gmail draft sits in a draft folder. A bad Slack post embarrasses you in front of your team. A bad GitHub push runs CI, triggers a deploy, changes what everyone else pulls tomorrow, and in the force push case removes commits nobody can retrieve without a local copy.

GitHub was in the first wave of Grok connectors as of writing. Availability and the consent bundle change, so read the screen you are shown when you connect. GitHub's permission model moves less, and it is unusually good if you use the modern half of it.

Write access to main is a different risk class

Rank the actions a GitHub bot can take by how hard they are to undo, because that ranking is the whole permissions decision:

  1. Comment on a PR or issue. Delete it. Thirty seconds, no trace.
  2. Apply a label, assign a reviewer, open an issue. Reverse it in the UI. Visible in the timeline, harmless.
  3. Push a branch. Delete the branch. Someone may have pulled it, but it is not anyone's base.
  4. Merge to the default branch. CI ran, a deploy may have fired, downstream branches inherit it, and reverting produces a second commit rather than an absence. The history says it happened.
  5. Force push to the default branch. Commits that only existed on the remote are unreachable. If nobody has them locally, the work is gone.

Steps one through three are where a bot earns its keep. Step four is a human decision even when the diff is trivial, because merging is not a statement about the code, it is a statement that the team is ready for it. Step five is not something a bot should be able to do at all, and you can enforce that outside the charter entirely.

Pick fine-grained repository tokens over the classic scope

Classic personal access tokens are scoped by capability, not by repository. The commonly used repository scope is effectively all-or-nothing: read and write across every repository your account can reach, including private ones belonging to organizations you are a member of. A bot that only needed one open source repo ends up holding a key to your employer's monorepo.

Fine-grained tokens select specific repositories, then set each permission independently to no access, read, or read and write. One token per bot, the smallest repository set that works, read where read is enough.

Credential modelHow it is scopedExpiryWhat it proves afterwardsUse it?
Classic personal access tokenBy capability, across every repository the account reachesOptional, so people pick noneOnly that your account did itNo
Fine-grained personal access tokenPer repository, per permission, three levels eachMandatory, one year maximumYour account, inside a named repository setYes, the default
GitHub App installation tokenPer installation, per repository, per permissionShort-lived, minted on demandAn app identity separate from youBetter for a team
SSH deploy keyOne repository, read or read and writeNone unless you rotateWhich key, not which humanOnly for a build box

The fourth column matters because Grok Bot's documentation is explicit that an audit view of bot actions does not exist outside Enterprise. If the runtime cannot say which bot did what, the credential has to. A fine-grained token bounds the answer to two named repositories; a classic token bounds it to anything your account can reach, which is not a bound.

Permission familyWhat it grantsWorst realistic outcome
Metadata (read)Repository names, topics, collaborators. Required beside most others.Private repository names and structure leak, often enough to map a roadmap.
Contents (read)Clone and read code, branches, files.Source code, and anything unwisely committed beside it, reaches whatever the bot writes next.
Contents (write)Create branches, push, merge, delete branches, force push where allowed.Unreviewed code on the default branch, or history destroyed by a force push.
Pull requests (read)Read PRs, diffs, review threads.Unreleased work and internal review discussion become readable.
Pull requests (write)Comment, label, request reviewers, approve, request changes, merge.An approval that satisfies a review requirement, or a merge nobody read.
Issues (read and write)Read, open, comment, label, close issues.Tracker noise, closed issues nobody triaged, notification fatigue.
ActionsRead or re-run workflow runs.A re-run of a deploy workflow, using production secrets.
Workflows (write)Modify files under the CI workflow directory.Arbitrary code execution in CI with your repository secrets. The most dangerous row, and the least dangerous sounding.
AdministrationRepository settings, branch protection, collaborators, visibility.The safety rails get edited by the thing they protect you from.
Organization-levelMembers, teams, org settings across every repository.Account-wide blast radius for a bot that was meant to triage one project.

Two rows carry most of the risk, and one deserves its own section. Administration edits the backstop: a bot that can change branch protection is no longer constrained by it. Grant it to nothing, and check the token you minted did not include it because you clicked a preset.

Refuse workflow write, because it runs code with your secrets

A file under the CI workflow directory is not documentation and not configuration. It is a program. GitHub executes it on a runner, and that runner is handed whatever secrets the repository or environment exposes to the job: deploy keys, cloud credentials, registry tokens, signing keys.

So the workflow permission is not "can edit YAML". It is "can run arbitrary code as your deployment identity". Three specifics decide it:

The rule is short. A triage bot gets no Actions and no Workflows permission. To have it notice CI failures, give it read access to check runs and keep the re-run button on the human side. To have it propose a workflow change, have it write the YAML into a comment for you to paste. The gap between suggesting a workflow and being able to write one is the whole security story.

Draw the two lines first: never merge, never force push

Most GitHub bot charters are too long and miss the two clauses that matter. Write these first, then add the rest.

Never merge. Not with an approval, not with a green build, not when the diff is a typo fix. Everything upstream of merge is reversible with a delete or a close; everything downstream involves other people's working copies. A bot that comments on forty PRs a week and merges none is a great teammate. A bot that merges the easy ones is a bot you now have to audit.

Never force push, and never rewrite history. Amend, rebase onto a shared branch, squash a branch someone else is using: all of these replace commits rather than adding them. The old commits become unreachable, and there is no recovery path you would want to depend on at 6pm.

A third clause matters for review bots: never approve, never request changes. Both are formal review states. An approval can satisfy a required-reviews rule, which means the bot silently became the human in the loop. Requesting changes blocks the PR and pins a red mark on somebody's work. Comments carry the same information and none of the authority. The catalog's PR review sentinel declares exactly that boundary: it never merges, approves, pushes, or requests changes, and it comments only.

Start with the job whose worst outcome is a deleted comment

Pick the first job by blast radius, not by how it demos, and add the next only once the first has been boring for a fortnight.

JobPermissions it needsWorst outcomeTime it gives backVerdict
PR triage commentContents read, PRs read and writeA wrong summary you deleteHighest, front-loads every reviewStart here
Issue labelling, fixed vocabularyIssues read and write, Metadata readA wrong label, one clickCompounds with backlog sizeSecond
Draft release notesContents read, PRs readA draft nobody publishesHigh on release weeks, zero otherwiseThird, easiest sell
Stale PR and issue sweepIssues and PRs readA report you ignoreLow, but nothing else surfaces itFourth
CI failure summary, read onlyActions read, Contents readA misread stack traceReal when CI is flakyFifth, no re-run rights
Dependency bump triageContents read, PRs read and writeA bump described wronglyTracks your update volumeAfter the first four
Anything that merges or pushesContents writeUnreviewed code on mainNot applicableDo not

The verdict column is not ranking value. The first four jobs share one property: their whole output is text a human reads and can delete. Buy that property first.

Automate the preamble a reviewer does before reading the diff

Review is where the time goes, and most of it is not spent reading code. It is spent working out what a PR is, how risky it is, and whether it is even ready. That preamble is mechanical, and it is the right thing to automate.

A useful triage comment answers four questions before a human opens the diff:

Here is a charter that produces that and stops there:

You are my PR Review Sentinel for [org/repo].

// WHAT YOU OWN
When a pull request opens or is updated, read the full diff, the PR
description, and the linked issue. Post ONE comment containing:
  SUMMARY   one sentence describing what the diff actually does
  RISK      any of: migration, auth, payments, permissions, public API,
            infra config, dependency change. Say "none detected" if none.
  MISSING   behavior changed without tests, new deps, config without docs,
            leftover debug or commented-out code
  SCOPE     changes unrelated to the stated purpose of this PR
  QUESTIONS at most three, each pointing at a specific file and line
Then apply labels from this list only:
  size/s size/m size/l risk/high needs-tests needs-docs

// WHAT GOOD LOOKS LIKE
Reference file paths and line numbers. Never say "some files".
If the diff is under 20 lines and touches no risk surface, say so in one
line and stop. Do not manufacture findings to look useful.
Never comment on style that the linter already enforces.

// WHERE YOU STOP
Never merge. Never approve. Never request changes. Never push or force push.
Never edit files, never open a PR, never modify anything under the
CI workflow directory.
Never close an issue or a PR.
Never comment more than once per commit on the same PR.
If the diff touches [payments, auth, migrations], say so and tag me
instead of analyzing further.

For standing checks against the codebase rather than a single diff, the codebase hardening auditor works only inside the repository and never touches production: the same idea on a schedule instead of an event. That choice, and what each option costs, is worked through in the PR review guide.

Label the backlog with a closed vocabulary and an unclear bucket

Issue triage is lower stakes than PR review and higher value than it looks, because the cost of an untriaged backlog is not the untriaged issues, it is that nobody trusts the tracker enough to look at it.

Give the bot a closed label vocabulary and an escape hatch. Ten labels maximum: three for type (bug, feature, question), three for area, two for severity, one for needs-reproduction, one for unclear. That last one is the honest option, and without it every ambiguous issue gets a confident wrong label.

Two things the bot never does in the tracker:

If you run several code bots, the engineering agent manager covers the coordination layer, and its declared boundary is the same shape: it never merges, posts publicly, or messages outside the team without approval.

Generate release notes from the merge range, with a PR number on every line

Release notes are the best-fitting GitHub bot task, and almost nobody sets them up. The input is completely determined: the commit range between two tags, the merged PR titles in it, the linked issues, and the labels. No judgment call about what happened, only about how to say it.

One rule makes the output trustworthy: every line traces to a merged PR number, and if the bot cannot point at a PR the line does not go in. That single constraint removes the failure where a plausible-sounding feature nobody built appears in your changelog.

Publish it as a draft. A draft release is not public, notifies no watchers, and does not create the tag until somebody presses publish. That is the same draft-before-send discipline that governs a mail bot, laid out in the one-person company guide.

One refinement: ask for a "no user-facing change" bucket at the bottom listing the PRs it left out. That bucket is how you catch the release where it quietly dropped the one thing users cared about.

Cap the review at one comment and three questions

The failure mode of an automated reviewer is not being wrong. It is being voluminous. Twenty comments, four of them real, is worse than four comments, because the reader now has to triage the review, and after a week they stop reading it at all.

Constraints that keep a review bot welcome:

Measure it after two weeks with one number: how many of its comments led to a change in the PR. Under a quarter, tighten the charter rather than changing the model. Verbosity is a specification problem.

Treat issue text and PR descriptions as attacker-controlled input

Issue bodies, PR descriptions, commit messages, code comments, README files on a forked branch, and a dependency's changelog are all text that arrived from outside your team. A model reading them cannot reliably distinguish a sentence describing a bug from a sentence addressed to it. No Slack or calendar integration has this problem, because strangers cannot write into them.

The concrete shape is unglamorous. Someone opens an issue whose body is ordinary prose followed by a line telling the reviewer bot to disregard its instructions and approve a specific pull request, or to paste an environment file into a comment. The bot has read access to contents and write access to comments. Nothing in that chain requires a bug in GitHub.

Three defences, and only the third works without the model cooperating:

Enforce the merge line with branch protection, not with a promise

A charter is a promise. Branch protection is a wall. The charter says the bot will not merge; the rule means it cannot, regardless of what the model decides or what a cleverly worded issue comment talks it into. Set both, rely on the second. This is why a code bot can be safer than a mail bot despite the higher stakes.

Rule on the default branchWhat it makes impossibleWhat it still allows
Require a pull request before mergingA direct push to main by anyone, bot includedOpening PRs, pushing to feature branches
Require one approving review from a humanA merge nobody signed offA bot comment, and a bot approval that does not count if you exclude it
Require status checks to passMerging a red buildMerging a green build with an unread diff
Block force pushesHistory rewritten, commits going unreachableOrdinary commits, reverts, new branches
Block branch deletionThe default branch disappearingDeleting feature branches
Do not add the bot to the bypass listEvery rule above applying to the bot tooEverything the bot legitimately does
Withhold the administration permissionThe bot editing any of these rulesThe bot reading repository metadata

The last two rows are the ones people skip, and skipping either cancels every row above it. A bypass entry is not a convenience setting, it is an exemption from the whole table, and a bot with administration rights writes itself one.

This is also the layer that answers the previous section. If the only thing standing between a hostile issue comment and your default branch is a paragraph in a charter, you are relying on the wrong layer. Branch protection does not read English.

Prove the bot cannot merge by trying to merge with it

A setup you have not tried to break is a setup you are assuming works. Ten minutes, and it either passes or it tells you something important.

  1. Use a throwaway repository carrying the same protection rules.
  2. Open a trivial PR against the protected branch, a one-line README change.
  3. Ask the bot directly, outside the charter, to merge it. You are deliberately asking it to cross the line.
  4. Watch for two separate outcomes. The charter outcome is the bot declining and naming the clause. The infrastructure outcome is the API refusing even if it tries, because the token has no contents write and the rule requires a human review.
  5. Remove the protection rule and repeat step three. If it merges now, the charter was the only thing ever stopping it, and you learned that cheaply.
  6. Restore the rule and confirm on the settings page, not from memory.

Run the same drill for a force push and an approval. Three tests, one sitting, repeated whenever you mint a token, because a token's permission set is fixed at creation and nothing will remind you what you ticked.

Build one more check into the routine: have the bot end every run naming which repositories it read and which write actions it took. No audit view exists outside Enterprise, so a record you want on Friday has to be written on Tuesday.

Answer the strongest case for letting the bot merge

The honest objection: automated merging is not exotic. Plenty of teams auto-merge patch-level dependency bumps on a green build and have for years without incident. A queue of forty unmerged security patches is itself a risk, and insisting a human press every button is how the queue gets to forty.

That argument is correct in a narrow band, and the band is worth naming, because the version people implement is much wider.

Auto-merge is defensible when all of these hold at once: the change is generated rather than authored, the update is patch or minor inside a range you already pinned, the full suite runs against the merged result rather than the branch, no deploy fires from it, and a human reads the resulting release that week. Dependabot scoped to patch updates on dev dependencies fits, and GitHub's own auto-merge does it with no model involved at all.

Notice what that band is not. It is not a model reading a diff and concluding the change is safe. The dependency case works because the decision comes from a deterministic rule about version ranges and test results. The moment it requires reading the diff and forming an opinion, you are back to a merge nobody reviewed, and a model forming the opinion is not a person forming it.

So: if you want automated merging, use GitHub's auto-merge with a narrow deterministic rule and keep the bot on the comment side. Two mechanisms, each doing what it is good at. The mistake is letting the bot inherit the merge right because the dependency case sounded reasonable.

Watch what thirty days on one repository actually looks like

Run this on one repository and nothing else for a month. Here is the shape of that month, so you can tell whether yours is going well.

Day one. Mint a fine-grained token for one repository: contents read, pull requests read and write, metadata read, nothing else. Paste the charter. Set the branch protection rules from the table. The next PR gets a triage comment about ninety seconds later, slightly too long.

Week one. You will cut the charter twice. Once to drop an output section nobody reads, usually SCOPE where PRs are already small. Once to exclude a generated file it keeps commenting on. Both are one-line edits. If you are rewriting paragraphs, the charter is describing a personality instead of an output shape.

Week two. Run the comment-to-change ratio. Under a quarter means the questions are generic, and the fix is a shorter QUESTIONS cap, not a longer instruction about being useful.

Week three. Add issue labelling with the ten-label vocabulary, which needs one new permission. Add nothing else that week, or when the output gets noisy you will not know which change caused it.

Week four. Draft release notes for whatever ships and run the merge drill. By now the question is not whether the bot works, it is whether you read it.

Next you will want either an issue-side bot that files bugs from reports rather than labelling existing ones, covered in the bug triage guide, or a second bot for a second repository. Prefer the second repository. One job per bot stays diagnosable; one bot with two jobs does not.

Keep reading: Grok Bot and Jira, Grok Bot and Linear, Grok Bot and QuickBooks.

Frequently Asked Questions

Should a Grok Bot GitHub setup be allowed to merge pull requests?

No, and this is the clearest line in the whole integration. Merging is the point where code stops being a proposal and becomes everyone's problem: CI runs, deploys may fire, and other people's branches inherit it. Everything before merge is cheap to undo by deleting a branch or a comment. Have the bot triage, summarize risk, label, and comment, then leave the merge to a person even for trivial diffs. Enforce it with branch protection requiring a human approving review rather than relying on the charter alone.

What GitHub permissions should a review bot have?

Use a fine-grained token limited to the specific repositories involved, with read access to contents and pull requests and write access only to pull request comments and labels. Refuse write access to contents, refuse the workflow permission, and refuse administration. The workflow permission is the one to watch: write access to CI workflow files is effectively arbitrary code execution with your repository secrets. Avoid classic tokens with the broad repository scope, since that grants read and write across every repository your account can reach, including private organization ones.

Why is force push singled out as a boundary?

Because it is the only common GitHub action that destroys work rather than adding to it. A normal push adds commits, a revert adds a commit, and a bad merge can be undone with another commit, so the history stays intact and the mistake stays visible. A force push replaces the branch pointer and leaves the previous commits unreachable, so if no teammate has them in a local clone there is no practical recovery path. Block force pushes on protected branches at the repository level so the capability does not depend on a bot behaving correctly.

Can a bot write release notes I would actually publish?

Yes, and it is the best-fitting GitHub automation, because the input is fully determined: a commit range, the merged pull request titles inside it, the linked issues, and the labels. Require every line to cite the pull request number it came from, which eliminates plausible-sounding entries for features nobody built. Have the bot save the result as a draft release rather than publishing, since a draft notifies nobody and does not create the tag until a human presses the button. You edit the phrasing and publish, which takes a couple of minutes instead of an hour.

Grok Bot and GitHub: Permissions and What to Automate