Artificial Intelligence

My Coding Agent Needed Its Own GitHub Identity

In my last post, I wrote that “the typing of code was parallelized and delegated. The judgement wasn’t.” That distinction has become an important part of how I use coding agents.

When I started using a coding agent seriously, one thing bothered me fast: it was acting as me on GitHub. My commits, my pushes, my PR authorship. Every branch protection rule I’d set up — required reviews, no direct pushes to main — became much weaker, because the thing pushing code was indistinguishable from me.

If the agent can push as me, it can bypass exactly the controls I put in place to keep agent-written code from landing without review.

I care about this because the loop only works if the review is real. The agent writes code, opens a PR, I review and approve. There has to be a genuine handoff — me looking at what the agent produced before it lands on main. And I don’t mean just the code. I pay more attention on the description of what was done, what was deffered, the follow up issues that were created (I instruct my agents to file new issues and refer to them if they punt on some work). When the agent operates as me, that discipline collapses and my visibility it what was actually done goes away. Branch protection is what enforces the handoff, and a separate identity is what makes branch protection meaningful.

Some agent experiences already have special integrations with GitHub’s Agents HQ model. Copilot, Claude, and Codex are treated as special integrations, and companies that want to participate have to go through GitHub’s Partner Program. I’ve already applied for Spring Voyage. But there is no public API that lets developers introduce their own custom agents as first-class GitHub agent identities. And even if GitHub eventually opens that door, today those custom agent experiences still depend on the GitHub Copilot licensing model.

Outside that set, your options narrow fast. The agent can act as you — the problem. You can create a “machine account” — but that’s a fake human user with yet another username and password to rotate, and GitHub still treats it as a person, not a bot. There’s no public API for third-party tools to offer their own first-class bot identities; I’ve argued there should be. Until that lands (if the feature request is accepted), you can roll your own GitHub App instead. It took me about 20 minutes.

I use Claude Code, but the rest of this works for any agent that shells out to `git` and `gh` — Codex CLI, Aider, Cline, your own scripts.

The pieces are old; the workflow is the point. GitHub Apps have existed for years, and people have wrapped them in CLIs before. I’m just sharing the specific setup that’s been working for me, in case it’s useful to someone in the same spot.

What You Get

A GitHub App is a first-class GitHub identity: its own username (savasp-agent[bot] in my case), its own avatar, its own presence in the commit and PR history. When the App pushes a branch or opens a PR, GitHub records it as the App — not you.

That one change makes branch protection meaningful again:

  • The App can’t approve its own PRs. When it opens one, a human has to review it.
  • “Dismiss stale reviews when new commits are pushed” works correctly — if the App pushes a fixup commit, my approval is cleared.
  • Every merge to main has a paper trail: a PR opened by the bot, reviewed and approved by a human, merged by GitHub after CI passed.

The important part is not just creating the App; it is also not giving the App bypass rights on main. The App doesn’t touch main unless CI is green and a human signed off. Not bureaucracy — just good practice that the tooling enforces, so I don’t have to remember to.

This does not make the agent trustworthy. It makes the handoff trustworthy. The agent can still write bad code, misunderstand instructions, or open a flawed PR. The point is that GitHub can now tell the difference between the agent’s work and my approval of it.

Creating the GitHub App

In GitHub: Settings → Developer settings → GitHub Apps → New GitHub App.

The meaningful choices:

  • Permissions — Contents (read/write), Issues (read/write), Pull requests (read/write), Metadata (read-only). That’s all it needs.
  • Webhook — disable it.
  • Installation scope — “Only on this account” for personal repos, or org-level if you’re working in an org.

Install it only on the repositories where the agent should be allowed to work. Don’t give it org-wide access unless you actually need that.

After creation, generate a private key (download the .pem) and note the App ID. Then install the App on your repos and note the Installation ID — it’s in the URL on the installation settings page, or via:

curl -H "Authorization: Bearer <your-jwt>" https://api.github.com/app/installations

Authentication

GitHub Apps use short-lived tokens rather than persistent credentials, which is a nicer security model. The flow: sign a JWT with your private key (10 minutes), exchange it for an installation access token (1 hour), use that token wherever you’d use a PAT. JWT signing only needs openssl on your PATH — no extra dependencies. Tokens cache locally with chmod 600 and refresh shortly before expiry. The full code is in the repo at the bottom; it’s about 60 lines.

The `gh-app`Wrapper

The catch with App tokens: you can’t configure them in Git’s credential store. They expire, and you don’t want them persisted anyway. Every push needs the token injected at call time without touching the keychain.

I wrote a small Python CLI called `gh-app` for this:

> gh-app push # push HEAD as App identity
> gh-app pr create -- --title "..." --body "..." # opens PR, adds reviewer, enables auto-merge
> gh-app issue create -- --title "..." --body "..."
> gh-app issue link 57 --sub-issue-of 40 --blocked-by 55

For pushes, it rewrites the remote URL to embed the token and disables the credential helper. For gh commands it sets GH_TOKEN in the subprocess environment. The token never hits the command string, so it doesn’t leak into shell history.

pr create auto-adds me as reviewer and immediately enables auto-merge with squash. The happy path becomes: the agent opens the PR, I review, I approve, GitHub merges when CI passes — no manual merge step.

For anything not covered by the wrapper:

GH_TOKEN=$(gh-app token) gh <whatever>

I also configure the git author and committer identity in the agent environment so the commits and PR actor line up. Otherwise, the PR may be opened by the App while the individual commits still appear to come from a different configured git identity.

Wiring It Into Your Agent

The last step is telling the agent to use gh-app instead of bare git push or gh. For Claude Code, this goes in CLAUDE.md:

## GitHub identity

Use the `savasp-agent` GitHub App identity for all GitHub write operations.
Never run a bare `git push` or use `gh` directly for writes.
gh-app push
gh-app pr create -- --title "..." --body "..."
gh-app issue create -- --title "..." --body "..."

For other agents, the same pattern goes wherever you put system instructions — Codex CLI’s AGENTS.md, Aider’s conventions file, Cursor rules, whatever your tool reads.

In practice, the agent follows this reliably most of the time but occasionally reverts to familiar patterns — bare git push, direct gh pr create — particularly in longer sessions. Pointing it back to the instructions is part of the workflow. The agent will close and recreate the PR. The wrapper failing loudly on auth issues catches the rest.

From Personal Wrapper to Product Feature

This pattern is also the one I used in Spring Voyage v1, and it will become part of Spring Voyage v2’s GitHub Connector. The personal gh-app wrapper is the smallest version of the idea; Spring Voyage applies the same model at the platform level.

In v1, Spring Voyage appeared on a user’s repository as a separate identity because it was backed by a Spring Voyage GitHub App. Users did not have to create their own App, manage private keys, or wire tokens into a local CLI. They followed an installation link, chose the repositories Spring Voyage should be allowed to access, and GitHub handled the rest.

Once installed, the experience felt much closer to working with a human collaborator than calling an external automation service. Users could assign issues to @spring-voyage, mention @spring-voyage in comments, and interact with it inside the normal GitHub workflow. Behind the scenes, Spring Voyage listened to GitHub webhooks and supplemented them with polling through the GitHub API where needed. The important part was not the transport mechanism; it was the product shape: the agent had a recognizable identity, bounded repository access, and a workflow that fit into GitHub rather than sitting beside it.

That is the direction I want v2’s GitHub Connector to continue. A coding agent should not have to impersonate the user to be useful. It should be installable, mentionable, assignable, auditable, and constrained by the same review process as any other contributor.

The Workflow

The whole thing is intentionally simple:

Agent writes code → 
gh-app push → 
PR opened by GitHub App → 
human review → 
CI → 
auto-merge

That is the boundary I want. The typing of code can be delegated. The judgement stays with me.

The Payoff

My branch protection on main:

  • PR required, no direct pushes, no bypass for admins.
  • 1 approval required; stale reviews dismissed on new commits.
  • Status checks must pass.
  • Squash merge only.
  • Auto-merge enabled.

In practice: the agent does the mechanical work — writes code, runs checks locally, opens the PR. I do the judgment work — read the diff, decide, approve. GitHub does the merge. Nothing broken reaches main, because the workflow physically prevents it, and nobody has to remember to follow process — the process is just what the tools do.

The collaboration is also more legible. The commit history shows exactly what the agent produced and what I signed off on. That clarity matters — for catching mistakes, and for building confidence in the workflow over time.

The goal is not to make the agent autonomous. It is to make the boundary between agent work and human judgment visible and enforceable.

Savas Parastatidis

Savas Parastatidis works at Amazon as a Sr. Principal Engineer in Alexa AI'. Previously, he worked at Microsoft where he co-founded Cortana and led the effort as the team's architect. While at Microsoft, Savas also worked on distributed data storage and high-performance data processing technologies. He was involved in various e-Science projects while at Microsoft Research where he also investigated technologies related to knowledge representation & reasoning. Savas also worked on language understanding technologies at Facebook. Prior to joining Microsoft, Savas was a Principal Research Associate at Newcastle University where he undertook research in the areas of distributed, service-oriented computing and e-Science. He was also the Chief Software Architect at the North-East Regional e-Science Centre where he oversaw the architecture and the application of Web Services technologies for a number of large research projects. Savas worked as a Senior Software Engineer for Hewlett Packard where he co-lead the R&D effort for the industry's Web Service transactions service and protocol. You can find out more about Savas at https://savas.me/about

Recent Posts

Rebuilding My AI Team in Twelve Days — And Why

In February, I wrote about the small team I'd stood up instead of hiring humans:…

5 days ago

How I Built My Own Team of AI Developers

Assembling a dream team without a single hire I've been making great progress on CVOYA's…

2 months ago

Reflecting on 2025: Building CVOYA’s Future with AI Coding Agents

As 2025 is now behind us, I wanted to share a few reflections from my…

4 months ago

DIY smart home accessory – It all started with a question to ChatGPT

Few months ago, we bought a sculpture from a local art fair for our Palm…

4 months ago

The Beginning of CVOYA

There’s a unique energy that comes with starting something new — a blend of excitement,…

6 months ago

Enhancements in Graph Model: Dynamic Entities & Full-Text Search

As I continued work on BrainExpanded and its MCP service, I came to realize that…

10 months ago