GitHub CLI can now hand out tokens that die within hours. Turn it on before your agent leaks one
gh 2.103.0 adds opt-in expiring OAuth credentials. One command enables it, and three habits break if you don't change them first.

Every coding agent that can run gh can run gh auth token. That prints the OAuth token the CLI stored when you signed in, and until now that token stayed valid until somebody revoked it. A prompt-injected agent, a leaked transcript or a careless env dump would hand out a credential with no clock on it.
GitHub CLI 2.103.0, released on 21 October, adds an opt-in alternative: a short-lived access token paired with a rotating refresh token. GitHub says a leaked access token becomes unusable within hours. The exact lifetime isn’t published on the docs page or the changelog, so don’t build anything that assumes a number.
It’s a preview, it’s opt-in, and it changes how a few common scripts behave. Here’s what to switch on and what to check first.
Turn it on
For a single sign-in:
gh auth login --expiring
To make every future sign-in ask for expiring credentials:
gh config set expiring_credentials enabled
Both need gh 2.103.0 or later. If the server can’t issue refreshable credentials, gh falls back to a normal non-expiring token and prints an informational message, so check the result rather than assuming it worked. The changelog lists github.com and ghe.com as supported today, with GitHub Enterprise Server issuing them from 3.23. An older GHES instance quietly gets the old behaviour.
The release notes also record that the flag started life as --short-lived and was renamed to --expiring before release. Older blog snippets and gists using the first spelling won’t work.
Existing sign-ins aren’t touched. You have to sign in again to get the new kind of credential.
Check what you got
gh auth status now reports whether the credential is refreshable and when the current access token expires. Per the docs it also refreshes a token that is expired or close to it, and takes --no-refresh if you want to look without triggering a refresh. --json and --show-token exist too, and the token is hidden by default.
If you don’t see an expiry line, the sign-in fell back to a plain token.
Git over HTTPS needs one more step
If git pushes over HTTPS use the credential gh stored, gh has to be git’s credential helper so it can serve a fresh token each time:
gh auth setup-git
The changelog strongly recommends git 2.46 or newer. Older versions still work but print a one-time upgrade recommendation. The reason is caching: a git credential cache that holds an expiring token past its lifetime produces authentication failures that look random. Check git --version before you opt in, particularly on CI images and dev containers, which tend to lag.
Three things that break
Anything that copies a token out of gh and keeps it will eventually hold a dead one. The release notes say so directly: expiring access tokens shouldn’t be cached, and scripts should call gh auth token each time they need one. The command refreshes the token if it needs to before printing it.
# Fragile: the value goes stale inside a long-running process
export REPO_TOKEN="$(gh auth token)"
long-running-tool --token "$REPO_TOKEN"
# Safer: ask for a token at the moment of use
curl -sS \
-H "Authorization: Bearer $(gh auth token)" \
https://api.github.com/user
That’s the first breakage, and it covers MCP server configs, .env files, Docker build args and anything else that captured the output once. Search your dotfiles and agent settings for gh auth token and see which results land in a variable that outlives the command.
Second, GH_TOKEN and GITHUB_TOKEN are never refreshed. The release notes are explicit, and the docs say gh uses the variable in place of stored credentials. Those are the usual way a CI job or a hosted agent session authenticates, so the feature does nothing for them. It protects the credential on your own machine, the one a local agent can read, and leaves the environment-variable pattern as it was.
Third, changing scopes can undo it. The docs say that gh auth refresh --scopes needs --expiring or the config setting, or the expiring credential may be replaced with a non-expiring one:
gh auth refresh --expiring --scopes repo,workflow
With expiring_credentials set to enabled you can drop the flag, but it does no harm to be explicit in a script.
Tell the agent about the change too, or it will diagnose the first expired token as a permissions problem. A short note in CLAUDE.md or AGENTS.md is enough:
## GitHub auth
- `gh` uses an expiring credential. If a call returns 401, run `gh auth status` before anything else.
- Never store the output of `gh auth token` in a file or variable. Call it inline where it's needed.
What it does for an agent, and what it doesn’t
The honest framing is that it shortens the window. An agent that can run gh auth token can still read a working token at that moment, because the command’s job is to give you one. Expiry matters afterwards: a token pasted into a pastebin, written to a log or sent to an attacker’s server stops working within hours instead of lasting until you notice.
That’s worth having, since the leak usually isn’t noticed on the day. It isn’t containment. If your agent runs with your full personal sign-in, a hostile instruction can still use the credential for as long as the session lasts, and it can still push, open pull requests or delete branches within whatever scopes you granted.
The controls that limit what an agent can do with a token are the ones you already have: a narrow scope list, a separate machine or container, and permission rules on the commands that touch credentials. In Claude Code you can add a deny rule for gh auth token, but the permissions docs describe Bash rules as matching command text and not as a security boundary, so treat it as a speed bump. The expiry is the backstop for when the speed bump fails.
GitHub also says it plans to make expiring credentials the default for new sign-ins in a future release, with no date given. Opting in now means finding the cached-token scripts on your schedule instead of after an update.
Try it this week
Run gh --version and git --version. If gh is below 2.103.0, upgrade it, then run gh auth login --expiring and confirm gh auth status shows an expiry. Grep your shell profile, .env files and agent MCP settings for gh auth token and GH_TOKEN, and move any captured value to a call at the point of use. Then work normally for a day and see which tool complains first.
If a daemon or a CI job relies on a long-lived token, leave that one alone. Opting in is a per-sign-in choice, and existing credentials and manually supplied tokens keep working as before.