chrome-webstore-upload "invalid_grant" / bad refresh token in CI
chrome-webstore-upload exchanges your client id, client secret, and refresh token for a short-lived access token. "invalid_grant" means Google rejected that exchange, so no upload can proceed.
What this error means
The upload step fails during token exchange with "invalid_grant" or "Token has been expired or revoked", before any package is sent.
Error: invalid_grant
{
"error": "invalid_grant",
"error_description": "Token has been expired or revoked."
}Common causes
The refresh token expired or was revoked
Refresh tokens for testing-mode OAuth clients expire, and changing the client or scopes invalidates an old token.
Client id/secret do not match the token
The refresh token was minted for a different OAuth client than the one configured in CI, so the grant is invalid.
How to fix it
Mint a fresh refresh token
- Re-run the OAuth consent flow for your Web Store API client to get a new refresh token.
- Update the CI secret with the new token and matching client id/secret.
- Publish the OAuth client (out of testing mode) so the token does not expire.
env:
CLIENT_ID: ${{ secrets.WEBSTORE_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.WEBSTORE_CLIENT_SECRET }}
REFRESH_TOKEN: ${{ secrets.WEBSTORE_REFRESH_TOKEN }}Keep id, secret, and token from one client
Regenerate all three together so the refresh token always matches the client that issued it.
chrome-webstore-upload upload --source dist.zip --extension-id "$EXT_ID"How to prevent it
- Use a published (not testing-mode) OAuth client so tokens persist.
- Store client id, secret, and refresh token together as a matched set.
- Rotate and re-mint the token before it expires.