# GitHub's new OIDC subject format: fixing AWS trust policies

> Not authorized to perform sts:AssumeRoleWithWebIdentity on a new repo? GitHub added owner and repo IDs to the OIDC sub claim. How to check and fix the policy.

URL: https://crunu.com/blog/github-oidc-subject-claim-format
Language: en
Updated: 2026-10-30

If a GitHub Actions job fails with "Not authorized to perform sts:AssumeRoleWithWebIdentity" in a repository created after July 15, 2026, or one that was renamed, transferred or opted in since, a likely cause is GitHub's new subject claim format. The `sub` claim now carries numeric IDs, `repo:octo-org@123456/octo-repo@456789:ref:refs/heads/main` instead of `repo:octo-org/octo-repo:ref:refs/heads/main`, so an AWS trust policy that matches the old string no longer matches. The fix is to add the new format to the role's trust policy, or better, to check the immutable `repository_id` and `repository_owner_id` claims that AWS STS now exposes as condition keys.

This post covers what changed, how to see which format a repository uses, a policy that works for both, and why a wildcard across the whole organization is the wrong way to make the error go away. The guide to [CI/CD secrets with OIDC](https://crunu.com/guides/ci-cd-secrets-with-oidc) covers the wider setup across CI systems and clouds.

## What GitHub changed

GitHub announced the change in its changelog on April 23, 2026, and updated it on June 10, when the separator between name and ID became `@` ([GitHub changelog](https://github.blog/changelog/2026-04-23-immutable-subject-claims-for-github-actions-oidc-tokens/)). The OIDC reference gives the reason: the OpenID Connect specification requires subject claims to be locally unique and never reassigned, but the old format used only names, and "if a namespace was recycled, a different owner could create the same subject value" ([GitHub: OpenID Connect reference](https://docs.github.com/en/actions/reference/security/oidc)).

The new rules, in short:

- **New repositories** created after July 15, 2026 use the immutable format automatically.
- **Renames and transfers** after July 15, 2026 also move a repository to the new format.
- **Existing repositories** keep the old format "unless you opt in", at the organization or repository level, in the OIDC settings or through the REST API.
- **GitHub Enterprise Server** is not included.

The syntax is `repo:OWNER@OWNER-ID/REPO@REPO-ID:ref:refs/heads/BRANCH`. GitHub chose `@` "because @ cannot appear in GitHub usernames or repository names." Only `sub` changes; other claims such as `repository` and `job_workflow_ref` keep their name-based values.

This is why the breakage looks random. One team's new service fails on its first deploy while the old services, with an identical workflow, keep working.

## Check which format a repository uses

The repository's OIDC customization endpoint returns two useful fields: `use_immutable_subject`, and `sub_claim_prefix`, described as "the current `sub` claim prefix for this repository" ([GitHub REST API: OIDC](https://docs.github.com/en/rest/actions/oidc)).

```sh
gh api repos/kiteline/web-app/actions/oidc/customization/sub
```

If the prefix contains `@` and a number after the owner and the repository, the repository is on the new format. Kiteline is a fictional company; use your own owner and repository names.

You will need the numeric IDs for the policy. The repository API returns both:

```sh
gh api repos/kiteline/web-app --jq '.owner.id, .id'
```

To see every claim a job actually receives before touching the cloud side, GitHub suggests the `github/actions-oidc-debugger` action, which "requests a JWT and prints the claims included within the JWT".

## A trust policy that accepts both formats

During a migration you may have repositories on both formats behind one role. The simplest fix lists both subjects explicitly:

```json
{
  "Effect": "Allow",
  "Principal": {
    "Federated": "arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com"
  },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {
    "StringEquals": {
      "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
      "token.actions.githubusercontent.com:sub": [
        "repo:kiteline/web-app:ref:refs/heads/main",
        "repo:kiteline@123456/web-app@456789:ref:refs/heads/main"
      ]
    }
  }
}
```

A list inside `StringEquals` matches if either value matches. Once the repository is on the new format and deploys work, remove the old line.

A sturdier fix doesn't depend on the `sub` string at all. AWS STS now maps individual GitHub claims to condition keys, including `repository_id`, `repository_owner_id`, `ref`, `environment` and `job_workflow_ref`. The IAM documentation says repository IDs "are immutable and don't change even if the repository is renamed", and it recommends using such IDs because "a name that is freed by renaming or deletion can be claimed by a different account" ([AWS: IAM and STS condition keys](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html)).

```json
"Condition": {
  "StringEquals": {
    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
    "token.actions.githubusercontent.com:repository_owner_id": "123456",
    "token.actions.githubusercontent.com:repository_id": "456789",
    "token.actions.githubusercontent.com:ref": "refs/heads/main"
  }
}
```

This policy works the same whichever `sub` format the repository uses, keeps working after a rename, and refuses a new repository that someone creates under a recycled name.

*Figure: Diagram of a GitHub Actions job signing in to AWS STS with OIDC. Step 1: the job asks the GitHub Actions issuer for an ID token. Step 2: the issuer signs a JWT naming the repository kiteline/web-app and the branch main. Step 3: the job presents the JWT to AWS STS, which checks the issuer, the audience, the repository and the branch. Step 4: AWS STS returns short-lived credentials, and no long-lived cloud key is stored in CI.*

## Switching existing repositories without a failed deploy

Repositories created before July 15, 2026 will keep working on the old format until something moves them: a rename, a transfer, or an opt-in. It is better to choose the moment yourself than to find out during an incident, so move them one at a time:

1. Update the role's trust policy to accept the new format, using one of the two policies above.

2. Opt the repository in. Through the API, the same endpoint takes a `use_immutable_subject` flag:

   ```sh
   gh api -X PUT repos/kiteline/web-app/actions/oidc/customization/sub \
     -F use_default=true -F use_immutable_subject=true
   ```

3. Run the deploy workflow and confirm the role is assumed.

4. Remove the old-format subject from the policy.

`use_default` is required by the endpoint. If the repository uses a custom subject template, send `use_default=false` with its existing `include_claim_keys` instead, so the opt-in doesn't reset the template. GitHub notes that with immutable subjects, the owner and repository IDs are always part of the `repo` segment, even in a customized subject.

## Don't fix it with a wildcard across the organization

The tempting fix for "the subject doesn't match" is to loosen the match. One write-up of the breakage shows a policy that matched `repo:my-org/*` and fixed it by adding `repo:my-org@<your-org-id>/*` ([DEV Community, aws-builders](https://dev.to/aws-builders/github-changed-its-oidc-subject-claims-and-broke-my-aws-deploys-for-new-repos-2cfp)). That restores the deploy, and it also keeps a role that any workflow in the organization can assume.

Count what `repo:my-org/*` admits:

- **Every repository** in the organization, including ones created next month by anyone who can create repositories.
- **Every branch**, including a feature branch where someone adds a step that runs commands as the role.
- **Pull request runs**, whose subject is `repo:ORG/REPO:pull_request`.
- **Every workflow file**, not just the deploy workflow.

A role that deploys production should match one repository, by ID, and either the protected branch or a deployment environment with required reviewers. Separate roles for separate jobs cost a few lines of policy each. If an organization-wide role is genuinely needed, for example to read a shared artifact bucket, give it read-only permissions that would do little harm in the wrong job.

## The same idea on GitLab and Bitbucket

The underlying lesson, match stable IDs rather than names, applies to the other CI systems too.

**GitLab CI.** The default `sub` is `project_path:{group}/{project}:ref_type:{type}:ref:{branch_name}`. GitLab's documentation recommends including "stable, unique identifiers such as `namespace_id` and `project_id` alongside path-based claims like `sub`". On GitLab.com, AWS accepts these as condition keys; on GitLab Self-Managed and Dedicated, only `sub` is supported as an AWS condition key. GitLab also blocks ID tokens when the configured `sub` contains a project path "that another project previously used", with the error "ID token issuance is disabled in CI because a project path in the `sub` claim was previously used by a different project". The fix is to put `project_id` first in the project's `ci_id_token_sub_claim_components` and update the trust policy to match ([GitLab: OIDC authentication using ID tokens](https://docs.gitlab.com/ci/secrets/id_token_authentication/)).

**Bitbucket Pipelines.** Atlassian's setup guide has you copy the workspace, repository and deployment environment UUIDs when configuring the resource server, rather than names ([Atlassian: Pipelines and OIDC](https://support.atlassian.com/bitbucket-cloud/docs/integrate-pipelines-with-resource-servers-using-oidc/)).

To draft a policy for your own repository and cloud, try the [OIDC policy builder](https://crunu.com/tools/oidc-policy-builder).

## How a Crunu CI binding handles it

Crunu uses OIDC the same way a cloud does, as a check on who is calling. A service token gets a **CI binding (OIDC)**, and the server accepts the token only together with a JWT from the CI provider whose claims match the binding's rules.

*Figure: Example: Priya created the service token github-actions-web-app; the window shows it only once with ready variables for GitHub Actions. In the kiteline/web-app pipeline on main, crunu run gets web-app staging and deploys, and the token works only with GitHub’s OIDC token for that repository and branch.*

The rules for GitHub Actions match individual claims, not the `sub` string: the repository, such as `kiteline/web-app`, the ref, the deployment environment, and optionally the workflow ref, repository ID and owner ID. So the new subject format doesn't break an existing binding, and adding the repository ID and owner ID pins the token to that exact repository even if the name is later reused. A replayed JWT is refused, and the replay raises an alert.

The binding answers the question this post is about, which job may sign in. It doesn't limit what a legitimate job does with what it receives, so each token should still read only the environments its pipeline needs. Details are on the [GitHub Actions integration page](https://crunu.com/integrations/github-actions) and in the glossary entries for the [OIDC subject claim](https://crunu.com/learn/oidc-subject-claim) and [IAM trust policy](https://crunu.com/learn/iam-trust-policy).

## Checklist

- [ ] Run `gh api repos/OWNER/REPO/actions/oidc/customization/sub` and note `sub_claim_prefix`
- [ ] Look up the repository and owner IDs
- [ ] Update each AWS role: match `repository_id` and `repository_owner_id`, or list both `sub` formats
- [ ] Keep `aud` pinned to `sts.amazonaws.com`
- [ ] Replace any `repo:ORG/*` subject on roles with write or deploy permissions
- [ ] Opt existing repositories in once their policies accept the new format
- [ ] Remove the old-format `sub` lines after deploys pass

## Sources

- [Immutable subject claims for GitHub Actions OIDC tokens, GitHub changelog](https://github.blog/changelog/2026-04-23-immutable-subject-claims-for-github-actions-oidc-tokens/), checked October 30, 2026.
- [OpenID Connect reference, GitHub Docs](https://docs.github.com/en/actions/reference/security/oidc), checked October 30, 2026.
- [REST API endpoints for GitHub Actions OIDC, GitHub Docs](https://docs.github.com/en/rest/actions/oidc), checked October 30, 2026.
- [IAM and AWS STS condition context keys, AWS IAM User Guide](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html), checked October 30, 2026.
- [GitHub changed its OIDC subject claims and broke my AWS deploys for new repos, DEV Community](https://dev.to/aws-builders/github-changed-its-oidc-subject-claims-and-broke-my-aws-deploys-for-new-repos-2cfp), checked October 30, 2026.
- [OpenID Connect (OIDC) authentication using ID tokens, GitLab Docs](https://docs.gitlab.com/ci/secrets/id_token_authentication/), checked October 30, 2026.
- [Integrate Pipelines with resource servers using OIDC, Atlassian Support](https://support.atlassian.com/bitbucket-cloud/docs/integrate-pipelines-with-resource-servers-using-oidc/), checked October 30, 2026.
