Rotating legacy GitHub tokens through the GitHub API
A practical pattern for resetting legacy GitHub tokens in-place, storing the replacement safely, and avoiding an expensive user re-auth flow.
Token migrations are the kind of maintenance work that looks tiny on paper and risky in production.
In this case, GitHub changed token formats and we needed a way to update existing user tokens without forcing everyone through a fresh installation or auth flow.
The constraint
Once you reset a token through the GitHub API, the old token is done. The replacement is returned in the response and you need to persist it immediately and safely.
If you lose that response or fail halfway through the write path, you have created more recovery work for yourself and more disruption for the user.
The core API call
await fetch(
`https://api.github.com/applications/${GITHUB_APP_CLIENT_ID}/token`,
{
method: "PATCH",
headers: {
Authorization:
"Basic " +
Buffer.from(
GITHUB_APP_CLIENT_ID + ":" + GITHUB_APP_CLIENT_SECRET,
).toString("base64"),
"Content-Type": "application/json",
},
body: JSON.stringify({
access_token: YOUR_USER_ACCESS_TOKEN,
}),
},
);
The mechanics are straightforward. The operational discipline is the real story.
What matters in production
- Treat the returned token like a one-shot response that must be persisted safely.
- Build an auditable migration path instead of mutating records blindly in-place.
- Expect rate limiting and retries.
- Separate migration logic from user-facing traffic.
A pattern I liked here was using a lightweight local store as a recovery buffer. That gave us a record of what had been reset before updating the primary datastore.
Why I still like this approach
Forcing a full re-auth or reinstall flow is sometimes fine, but it is not free:
- it creates user friction
- it creates support load
- it turns a backend maintenance task into a product disruption
If the upstream API gives you a safe migration path, it is usually worth taking.
The lesson
Security-related maintenance work is rarely about clever code. It is about preserving control over state transitions.
In migrations like this, the important questions are:
- what becomes invalid immediately
- what must be captured atomically
- how do you recover if the process breaks halfway through
That mindset is more valuable than the API call itself.