AWS said AccessDenied. CloudTrail said why.
I renamed a branch, pushed, triggered a workflow, and got this:
Could not assume role with OIDC: Not authorized to perform sts:AssumeRoleWithWebIdentity
No other details. Not which condition failed, not what claim was wrong, not what the trust policy expected. Just “not authorized.” The workflow retried the assume-role 12 times with exponential backoff before giving up.
The error message gives you nothing to work with.
Check CloudTrail instead
CloudTrail → Event history → filter by Event name: AssumeRoleWithWebIdentity. The failed event is usually there within 15 minutes.
The event’s requestParameters shows exactly what OIDC claims were presented:
{
"roleArn": "arn:aws:iam::123...:role/GitHubActions-Terraform",
"roleSessionName": "GitHubActions",
"webIdentityToken": "...",
"subjectFromWebIdentityToken": "repo:ramparts/infra:ref:refs/heads/na-421-waf-body-size-limit"
}
And the trust policy on the role allowed:
"StringLike": {
"token.actions.githubusercontent.com:sub": [
"repo:ramparts/infra:ref:refs/heads/main",
"repo:ramparts/infra:ref:refs/heads/feat/*",
"repo:ramparts/infra:ref:refs/heads/fix/*"
]
}
The branch was na-421-waf-body-size-limit. The trust policy required feat/*. One glance at CloudTrail, problem solved. Renamed to feat/na-421-waf-body-size-limit, workflow passed.
Beyond OIDC
This works for any AWS AccessDenied, not just GHA OIDC. CloudTrail shows the exact API call, principal, and condition that was rejected — AssumeRoleWithWebIdentity, cross-account assumes, any denied API call. The error message gives you the symptom. CloudTrail gives you the cause.
The system was right
Our IAM trust policies are a deliberate branch-pattern access control layer. In the infra repo, feat/* and fix/* can run terraform plan and apply. The app repos are stricter — only releases/* branches can build and deploy container images to tenant environments. Different repos and branch patterns get different levels of infrastructure access, all enforced by OIDC subject claims.
The system was right to reject me. I forgot my own naming convention. That’s the whole point — I use GitHub Actions as an auditable infrastructure runner, and to trust it with that responsibility, the trust policy doesn’t trust anything I haven’t explicitly whitelisted.
Whenever AWS says “access denied” and nothing else, CloudTrail has the rest of the sentence.