google-github-actions/auth workload identity audience errors
A google-github-actions/auth workload identity audience failure is the first leg of the login being refused, inside the step itself, when Google's token service declines to exchange your GitHub token. The action reports the operation it was performing, which is what separates this from everything that happens afterwards.

What this error means
The auth step is red and nothing after it ran. The message begins with the action naming itself, then describes a failure to generate a federated token, then lists key and value pairs: the operation, an endpoint class, a status, an error class and an attempt counter. There is no Google service account in it and no IAM role, because the request never got as far as one.
Error: google-github-actions/auth failed with: Failed to generate Google Cloud federated token: operation=token_exchange, endpoint_class=sts.googleapis.com, status=400, error_class=invalid_request, attempt=3/3There are two audiences and they are not the same string
This is the detail that makes the error hard to reason about. The action computes one audience in main.ts for the token it requests from GitHub: the audience input if you set one, otherwise https://iam.googleapis.com/ followed by your provider resource name. It computes a second one inside the federation client for the exchange request body, as two slashes, then the IAM host, then the provider resource name.
So the audience input changes only the claim inside the GitHub token. The value Google is asked to validate against is derived from workload_identity_provider regardless. Setting the input to something Google does not expect, or setting it at all when it was not needed, is a common way to break a configuration that was working, and it is invisible unless you know the two values are computed in different places.
Common causes
The provider resource name uses the project ID instead of the project number
The most common configuration error, and it fails at this leg because the named provider does not resolve. The path is projects, then the number, then locations, then the pool and the provider. A path that reads correctly to a human and uses the ID is wrong.
The provider's attribute condition rejects this repository
A condition restricting the provider to particular repositories or owners is doing its job when it refuses, and the refusal looks identical to a misconfiguration. A condition that references an attribute the mapping never defines fails for a different reason and is worth checking at the same time.
The audience input was set and did not need to be
Overriding the audience changes the claim GitHub puts in the token while leaving the value sent to Google derived from the provider. Unless the provider was configured to expect that exact custom audience, the two no longer agree. Removing the input is often the repair.
The job never obtained an OIDC token to exchange
Worth ruling out first, because it produces a different message. Without id-token: write the action fails before the exchange, complaining about the request URL rather than about a federated token. If the message names the token exchange, a GitHub token was obtained and the permission is not the issue.
How to fix it
Confirm which leg failed before touching IAM
- If the message contains
operation=token_exchange, the failure is the first leg and no service account was involved. - If it names an impersonated credential instead, the exchange succeeded and the problem is downstream.
- If it mentions the OIDC request URL, no GitHub token was minted and the job permission is the issue.
Rebuild the provider path from the project number
Take the number from the project, not the identifier, and construct the full resource path. This single value is the most frequent cause and the easiest to verify.
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/123456789012/locations/global/workloadIdentityPools/github/providers/my-repo
service_account: deployer@my-project.iam.gserviceaccount.comRemove the audience input unless the provider expects a custom one
The default is derived from the provider you already named, so leaving the input unset keeps the two values consistent by construction. Set it only when the provider was deliberately configured with a non-default allowed audience, and then set both sides together.
Read the attribute condition against a real token's claims
The condition is evaluated against the claims in the GitHub token, so it can only reference attributes the mapping defines. Check that every attribute the condition uses is mapped, and that the values it compares against match the repository actually running the workflow.
Reading the key and value pairs
The action serializes the failure rather than narrating it, which is more useful than it looks once you know what each field is.
| Field in the message | What it tells you |
|---|---|
operation=token_exchange | The first leg. Impersonation has not been attempted and is not implicated |
endpoint_class | Which Google host answered, confirming the request left the runner |
status | The HTTP status from the token service, or none when nothing answered |
error_class | The classification the action assigned, which decides whether it retried |
attempt=n/m | How many times it tried; a full count means the failure was judged permanent or persistent |
What the token service is actually checking
The exchange asks Google to accept a JWT issued by GitHub as proof of identity. Google validates it against the workload identity provider named in the request: that the issuer is GitHub, that the audience matches what the provider expects, and that the provider's attribute condition is satisfied by the claims in the token. Any of those failing produces a refusal at this stage, before any service account is considered.
The provider resource name is where most configurations go wrong, because it must use the project number rather than the project ID. The two look interchangeable in a console and are not, and a provider path built with the ID names a resource that does not exist, which fails here rather than anywhere more informative.
Why there is no recorded run on this page
The exchange is a conversation between GitHub's OIDC provider and a Google project's token service about a provider resource we would have to create. Our pool, our attribute mapping and our attribute condition would decide the outcome, and the fields in the message that matter, the status and the error class, are answers about our configuration rather than yours. What is stable and worth reading once is where each of the two audience values is computed, and that is in the action's source, which we read instead.
How to prevent it
- Store the provider resource path as a single configuration value, so the project number cannot drift back to an identifier.
- Leave the audience unset unless the provider requires a custom one, and change both together when it does.
- Keep the attribute mapping and the attribute condition in one place, since the condition can only reference what the mapping defines.
- Grant
id-token: writeon the job, so a permission problem never masquerades as an exchange problem.
Frequently asked questions
Does the audience input control what Google validates?
What does status=none mean?
Why does the project number matter so much?
How do I tell this apart from an impersonation failure?
Related guides
References
- google-github-actions/auth: src/client/workload_identity_federation.ts, the exchange and its throw
- google-github-actions/auth: src/main.ts, where the requested audience is computed
- Google Cloud: workload identity federation with deployment pipelines
- GitHub Actions documentation
- Workflow syntax for GitHub Actions