You hand an agent in Antigravity a meaningful refactor, it works through the change cleanly, tests come back green, the commit message even reads like something you'd have written yourself — and then the final git push lights up red with remote: Permission to masakihirokawa/xxx.git denied to other-account. If you've spent any real time with Antigravity, you've probably felt that small deflating moment. I run four Dolice Labs sites in parallel — each with its own Personal Access Token — alongside the iOS and Android apps I've been shipping as an indie developer since 2014 (the AdMob revenue line on those apps now sits at 50M+ cumulative downloads). For Hirokawa, this category of failure has tripped me up more times than I'd like to admit.
The good news is that the request almost always reached GitHub's servers — it's just being rejected because the credentials the agent handed over don't match the repository. The less good news is that agent-driven workflows have more ways to get those credentials tangled than a human typing things by hand. What follows is the diagnostic order I actually run through, plus the structural fixes I rely on to stop this from recurring.
Read the error message first — it tells you where you're stuck
Before anything else, sort the red text into one of three categories. Each one points to a completely different layer of the problem.
remote: Permission to {owner}/{repo}.git denied to {user}: the request reached GitHub, the account on the other end was identified, but that account does not have write access to this repofatal: Authentication failed for 'https://github.com/...': credentials are missing or invalid — usually an expired PAT, a missing scope, or a typofatal: unable to access ... Could not resolve host/Connection timed out: this never made it out of your network — suspect a proxy or VPN, not Git
The first one is by far the most common in Antigravity workflows, so the rest of this article focuses there.
Find out who the agent is trying to push as
Agents are focused on the task you gave them; they have no opinion about which credential they're using right now. Your first job is to make the implicit choice visible.
# 1. Confirm the remote URL — HTTPS or SSH, and which owner
git remote -v
# 2. Confirm credential.helper is in play
git config --get credential.helper
git config --list --show-origin | grep -i credential
# 3. (macOS) See which GitHub credential the keychain actually serves
security find-internet-password -s github.com -g 2>&1 | grep "acct\|svce"Between these three commands you can usually narrate the situation in one sentence: "This repo is on HTTPS for masakihirokawa/xxx.git, but the keychain is handing back a PAT registered under acct=other-account." The moment the keychain's acct= and the remote's owner disagree, you've found the cause.
In multi-account setups like mine — same host, different identities — osxkeychain tends to act as if "github.com" should only have one credential. In my own logs, seven or eight out of ten Permission denied failures came down to a stale PAT camping out in the keychain.
Three workable patterns for keeping accounts separate
Once you know the cause, the next question is structural: how do you guarantee the agent uses the right credential every time, not just once? In practice I rely on three patterns. Each has its own trade-offs.
1. Embed the PAT directly in the remote URL (when you want full per-project isolation)
If you want a specific repo, in a specific clone, to always push under one specific PAT, the cleanest move is to put the credentials in the remote URL itself.
# Bind the masakihirokawa PAT to this clone, full stop
git remote set-url origin https://masakihirokawa:ghp_xxxxxxxxxxxx@github.com/masakihirokawa/example.git
# Verify
git remote -vThe win here is independence: it doesn't matter what your credential helper or keychain thinks — this clone will push under that PAT. Any terminal the agent opens in this repo will behave identically.
The cost is that the PAT lives in .git/config in plaintext. Don't do this in a shared clone, and don't include such configs in backups. I reserve this approach for short-lived clones under /tmp or strictly-local working copies that I would never share.
2. Per-URL credential routing (when you want to keep using the keychain but split accounts)
If you'd rather keep osxkeychain in the picture, you can ask Git to use different usernames for different URL prefixes. That's enough to coax the keychain into storing — and returning — a separate entry per account.
# Anything under github.com/masakihirokawa is "masakihirokawa"
git config --global credential.https://github.com/masakihirokawa.username masakihirokawa
# Anything under github.com/other-account is "other-account"
git config --global credential.https://github.com/other-account.username other-accountAfter setting this up, push once under each account so each gets its own keychain entry. From that point on Git will look at the remote's owner and pull the matching credential automatically.
This pattern plays nicely with workflows where the agent shallow-clones into a fresh path (in my case /tmp/repos/{site}) every run — the routing is keyed to URL ownership, not to any per-clone setting.
3. SSH with per-account keys via ~/.ssh/config (when you want the most durable answer)
If PAT expiry has worn you down, SSH is the calmest long-term home. Generate a key per account and route them via host aliases:
# ~/.ssh/config
Host github-masakihirokawa
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_masakihirokawa
IdentitiesOnly yes
Host github-other-account
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_other_account
IdentitiesOnly yesThen point each clone at the alias: git@github-masakihirokawa:masakihirokawa/example.git. No more PAT rotations creeping up on you at the worst moment.
The catch is that you can't really teach an agent to mentally rewrite git@github.com:... into git@github-masakihirokawa:... at clone time. I keep SSH for stable automation paths (my publishing pipeline runs on SSH) and use pattern 1 or 2 everywhere else.
How to keep agents from re-stepping into the same trap
Once the mechanics are sorted, the remaining work is operational — making sure an agent that knows nothing about your setup still does the right thing on the first try.
The single highest-leverage thing is documenting the contract in AGENTS.md (or .claude/AGENTS.md). One line like "This repo expects its remote to be set as https://masakihirokawa:${ENV_PAT}@github.com/... — do not run git remote set-url" will stop the agent from helpfully reconfiguring things into a broken state.
Next, keep the PAT itself out of code. I store mine in a separate file inside the workspace and pull them on demand with grep -A1 "Site Name" tokens.txt | tail -1. The agent reads the token at runtime; it never sits in a .env it might commit by reflex.
Finally, git config --global push.default current is a small but useful belt-and-braces setting. When the agent gets lazy and just types git push, it pushes the current branch — not whatever the upstream might think it wants. It removes a whole adjacent class of "wait, that didn't go where I expected" failures.
When Permission denied still won't go away
If you've worked through the patterns above and the error is still there, run this final three-item checklist:
- PAT scope and expiry: in GitHub → Settings → Developer settings → Personal access tokens, confirm the token is still valid and that it has the
reposcope - Org SSO authorization: for org-owned repos, generating the PAT isn't enough — you have to go to
Configure SSOand explicitly authorize it for that org - Duplicate keychain entries: open Keychain Access, search for
github.com, and delete stale PAT entries by hand. The keychain can hold multiple entries for the same host; asecurity find-internet-password -s github.combefore and after will show you what changed
I haven't yet run into a case where all three are clean and Permission denied survives. Whatever you find, write it down — a one-line memory file pays for itself the second time the same repo gives you the same error six months later.
Antigravity's agents are remarkable, but the authentication layer is one of those places where they still need humans to lay the groundwork. Put the structure in place once, and the rest of the year you get to focus on the actual work instead of re-typing PATs. I hope this saves someone else an evening.