GitHub App installation tokens are now about 520 characters (ghs_APPID_JWT), and X-GitHub-Stateless-S2S-Token stops working on November 30, 2026
Since October 2, 2026, every new GitHub App installation token is in the stateless format ghs_APPID_JWT (GitHub changelog; the rollout began on April 27). It still starts with ghs_, but it is about 520 characters long instead of 40, and contains two dots. Its permissions, its repositories, its one-hour expiry and the REST endpoint that creates it are unchanged. A workflow gets the new format too, for example from actions/create-github-app-token. What breaks is code that assumed the old shape: a length cap, a pattern, a redaction rule, a column. Until November 30, 2026, a request header can still ask for the old format; after that, GitHub ignores it.
What it looks like
- A length cap refuses the token. An update helper accepted a token only when it was 1 to 255 bytes, and every update through the app failed with "The request was refused: the token is malformed." (Quartio-Studio/yulius #423). The AWS Amplify API's
accessTokenwas capped at 255 characters (amplify-hosting #4106); its reference now says 4,096. Terraform'saws_amplify_appkept checking 255 on its own side; the change to 4,096 was merged on November 3 for the AWS provider's v6.72.0 milestone (terraform-provider-aws #49642). - A pattern for the old format does not match, such as exactly 40 characters, or
ghs_followed by 36 letters and digits, so a check rejects the token, or treats it as something else. - A redaction or secret-scanning rule written for the old format misses the token, or hides only its first part and leaves the rest visible. Many projects found this in their redactors in October, for example a gitleaks rule (melodic-software/standards #830) and a CLI's redaction (nils-cli #2339).
- A column, a secret store, an environment variable or a proxy with a small maximum truncates the token or the
Authorizationheader, and the request then fails as if the token were wrong.
Check a token without printing it
A stateless token is about 520 characters and has two dots; an old one is 40 characters with no dots. In a workflow, after an actions/create-github-app-token step with id: app-token:
- name: Token format
env:
TOKEN: ${{ steps.app-token.outputs.token }}
run: echo "length ${#TOKEN}, dots $(printf %s "$TOKEN" | tr -cd . | wc -c)"
This prints only the length and the number of dots. The action registers the token as a secret, so the runner masks it in the log in either format; your own scanners and log redactors are the ones to check.
The header, and November 30
X-GitHub-Stateless-S2S-Token is a temporary request header on POST /app/installations/{installation_id}/access_tokens, the endpoint that creates installation tokens (GitHub changelog, May 15, 2026):
enabled: returns a stateless token.disabled: returns an old, stateful token.- Absent, or any other value, such as
trueor0: the normal behavior, which is now the stateless token.
GitHub will stop honoring the header on November 30, 2026. From then on, a request with disabled gets a stateless token too. Code that sends disabled to keep a short token for a system with a length cap stops working that day, so fix that system first. To find such code:
grep -rn 'X-GitHub-Stateless-S2S-Token' .
GitHub says to remove the header from production code before November 30, once the app works with both formats.
Fix
- Treat installation tokens as opaque strings: no fixed length, no parsing.
- Make columns, secret stores, settings and header limits accept at least 520 characters, as GitHub's guidance for the new format says. The length varies, so leave room.
- Where a pattern is needed, GitHub recommends one that matches both formats:
ghs_[A-Za-z0-9\.\-_]{36,}. - Update redaction and secret-scanning rules to cover the whole token, dots and underscores included.
- For AWS Amplify through Terraform, use an AWS provider version with the 4,096 limit (v6.72.0 or later, by the milestone above).
Telling it apart
- The token is cut, refused as malformed, or not matched, and works when it is short: the new format, this page.
- "Bad credentials" (HTTP 401) with a token that has the right length: the token expired after its hour, or was revoked ("Bad credentials").
- "Resource not accessible by integration" (HTTP 403): the token works, and lacks a permission ("Resource not accessible by integration").
See failed jobs from IntelliJ IDEA
Pipeline Inbox for GitHub Actions Workflows, a plugin by Lesstep for JetBrains IDEs 2024.1 or later, lists the runs of all your repositories in an IDE tool window, with their jobs and steps, so a step that started failing after a change on GitHub's side stands out, and your current branch's runs are in the status bar. Reading is free; re-running failed jobs, cancelling, running workflows and reviewing deployments are Pro (US$1.50 a month, with a 30-day trial).
Get it on JetBrains Marketplace
GitHub and GitHub Actions are trademarks of GitHub, Inc.; IntelliJ IDEA is a trademark of JetBrains s.r.o.; AWS and AWS Amplify are trademarks of Amazon.com, Inc. or its affiliates; Terraform is a trademark of HashiCorp, Inc. This page is not affiliated with, sponsored or endorsed by any of them.
More: "Bad credentials" · "Resource not accessible by integration" · GitHub Actions plugins for IntelliJ IDEA compared · 日本語