Skip to content
← All posts

AI agent GitHub access without handing over your token

Kent C. Dodds · September 11, 2026

Here's a request I see all the time. An engineer wants their agent to pull open PRs across a few repos every morning and summarize what's blocked on review. Reasonable. So the agent asks for a GitHub token, the engineer goes to Settings, Developer settings, clicks "Generate new token (classic)," checks repo because that's the box that makes errors go away, and pastes the result into the chat. That's the default way teams grant AI agent GitHub access, and it's worth fixing.

That token can now push to every private repo the engineer can reach, in every org they belong to, until somebody remembers to delete it. And the plaintext is in a chat transcript.

The task needed read access to pull requests in four repos. The token grants something much closer to "be this engineer on GitHub." This post is about closing that gap without making the agent less useful.

Two separate problems with AI agent GitHub access

It helps to pull this apart, because people tend to solve one and assume they've solved both:

  1. Scope. What is the token allowed to do at GitHub? That's decided when the token is created, and only GitHub can enforce it.
  2. Custody. Who can read the token? Today, the answer is usually "the model, the transcript, and whatever file it ended up in."

A narrowly scoped token pasted into chat is still a leak waiting to happen. A token nobody can read but that can delete repos is still a big blast radius. You want both: a token that can only do the job, held somewhere the agent can use it without seeing it.

Step 1: make a token that matches the job

GitHub's fine-grained personal access tokens are the right tool here. The classic OAuth App scopes are coarse: the provider guide in the Kody docs calls out that repo is the only scope that reaches private repositories, and it includes write. Fine-grained tokens let you pick repositories and permissions one by one.

For the morning PR summary:

  1. Go to github.com/settings/personal-access-tokens/new.
  2. Set Resource owner to the org that owns the repos.
  3. Under Repository access, choose Only select repositories and pick the four repos.
  4. Under Repository permissions, set Pull requests and Contents to Read-only. Add Commit statuses or Checks read-only if you want CI results. Leave everything else at No access.
  5. Pick an expiration. 90 days is a sensible default for something you'll revisit.

If the agent's job grows later (say it should also comment on PRs), you edit the token's permissions at GitHub. You don't hand over a broader one "just in case."

Step 2: save it where the agent can use it but can't read it

This is where Kody comes in. Kody sits under whatever assistant your engineer already uses and holds credentials on its side. When you ask the agent to connect GitHub, a Kody-connected agent doesn't ask you to paste the token. It sends a link to a Kody page, prefilled with:

  • a name, like githubAccessToken
  • a description of what it's for
  • the approved host: api.github.com
  • optionally, an expiry that matches the one you just set at GitHub

You paste the token into that page. It never goes through the conversation.

From then on, code refers to the token by name. Kody fills the value in at the network boundary, only over HTTPS, and only for api.github.com:

const response = await fetch(
	'https://api.github.com/repos/acme/web/pulls?state=open',
	{ headers: { Authorization: 'Bearer {{secret:githubAccessToken}}' } },
)

If anything tries to send {{secret:githubAccessToken}} to any other host, the request is refused. If GitHub's response echoes the token, Kody redacts it before the code sees it. And there's no capability that returns the value, so there's nothing for an injected instruction to ask for. When the expiry passes, Kody treats the token as missing and stops sending it, which is a nice backstop if the GitHub side gets forgotten.

Step 3: turn the task into a package instead of a prompt

If the agent writes that fetch call fresh every morning, it works, but you're trusting the model to write the same code correctly every time, and you can't review what it'll do tomorrow.

The better move is to save the PR summary as a Kody package: a small, repo-backed TypeScript project with named exports. The package declares the credential it needs in package.json#kody.credentials, and at runtime it gets an opaque client for it:

import { credential } from 'kody:runtime'

export default async function openPullRequests(params: { repo: string }) {
	const github = await credential('github')
	const response = await github.fetch(
		`https://api.github.com/repos/${params.repo}/pulls?state=open`,
	)
	return response.json()
}

credential('github') returns a client with fetch, not a token. Kody picks the matching saved GitHub credential, and if there's more than one it stops and asks instead of guessing.

Now the behavior is code you can read, diff, and version. You don't have to start from scratch, either. There's a public @kody/github package in the Kody community you can fork into your account and trim down. A fork is a copy you own, so you can delete the exports you don't want the agent to touch.

Step 4: let it run without the agent

Once the package exists, the morning summary doesn't need an agent session at all. A Kody job can run the export on a schedule. No model is in that loop, so it does exactly what the code says, and the run shows up in your account's Activity page with who started it and how.

Why this is better for a team lead

Compare where you ended up with where you started:

Classic token in chatFine-grained token in Kody
What it can doWrite to every private repoRead PRs in four repos
Who can read itModel, transcript, config filesNobody, including the agent
Where it can be sentAnywhereapi.github.com over HTTPS
What runsWhatever the model writes todayA versioned package

The agent is just as useful. It can still read the repos, summarize the PRs, and tell you what's stuck. It just can't become you on GitHub, and it can't leak a token it never saw.

For the general version of this pattern across every provider, read least-privilege tokens for AI agents. For why keys in agent context are risky to begin with, start at Your engineers are handing private keys to AI agents.

Try it

Connect your agent to Kody, then ask it: "Set up GitHub in Kody with a fine-grained token for these repos, read-only pull requests." It'll walk you through the token page and send the save link. The full steps are in the GitHub provider guide.

More in this series:

  • Your engineers are handing private keys to AI agents. Here's what that means.
  • Least privilege for AI agents, a practical setup
  • A team lead's guide to AI agent credentials
  • How to revoke AI agent access in one place
Read nextCase studies

Give your assistant a home of its own. Create a free account and start saving packages.

Create a free account