Review with AWS Bedrock (Keyless OIDC)¶
Use this guide to run lgtmaybe with AWS Bedrock using GitHub's OIDC token. No static AWS credentials are stored in secrets.
Contents¶
- How it works
- One-time AWS setup
- Workflow example
- Choosing a Bedrock model ID
- Running locally with ambient AWS credentials
- Troubleshooting
How It Works¶
GitHub Actions issues a short-lived OIDC token. AWS STS exchanges it for
temporary credentials for an IAM role you control. Pass
aws_role_arn and the Action performs that exchange for you. lgtmaybe then
picks up the credentials through the standard AWS credential chain. There is no
AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY in your secrets.
One-Time AWS Setup¶
Do this once in your AWS account:
- Create an IAM OIDC identity provider for
token.actions.githubusercontent.com. - Create an IAM role with a trust policy scoped to your repository
(
repo:<org>/<repo>:*). - Attach the least-privilege policy below.
- Confirm the models you want are enabled in the target region (model access in the Bedrock console).
- Note the role ARN (e.g.
arn:aws:iam::123456789012:role/lgtmaybe-bedrock). It becomes theaws_role_arnAction input.
The role needs only:
{
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": [
"arn:aws:bedrock:*:<account-id>:inference-profile/*",
"arn:aws:bedrock:*::foundation-model/*"
]
}
Both ARNs are required for the recommended inference-profile model IDs (the
us./eu./apac.-prefixed IDs below). A cross-region inference profile routes
the call to the foundation model in several regions, so the role needs
bedrock:InvokeModel* on both the inference-profile/* ARN and the underlying
foundation-model/* ARN. Granting only one still fails with
AccessDeniedException. A bare anthropic.… model ID needs only the
foundation-model/* ARN. But most current Claude models can only be invoked
through an inference profile (see the model table below).
Once it works, scope each Resource to specific model and inference-profile
ARNs for tighter least privilege, e.g.
arn:aws:bedrock:*:<account-id>:inference-profile/us.anthropic.claude-opus-5-5*
and arn:aws:bedrock:*::foundation-model/anthropic.claude-opus-5-5*.
Workflow Example¶
Copy examples/workflows/review-bedrock.yml to
.github/workflows/lgtmaybe.yml. Store the role ARN in an AWS_ROLE_ARN
secret. The Action assumes the role itself, so you need no separate
configure-aws-credentials step. But the workflow must grant id-token: write
so GitHub will mint the OIDC token:
permissions:
contents: read
pull-requests: write
id-token: write # required for the OIDC token exchange (keyless auth)
The review step is:
- uses: MattJColes/lgtmaybe@v2
with:
provider: bedrock
model: us.anthropic.claude-sonnet-4-6
aws_role_arn: ${{ secrets.AWS_ROLE_ARN }}
aws_region: us-east-1
aws_region defaults to us-east-1. The example also carries the
trusted-author guard described in
who can trigger a review.
Choosing a Bedrock Model ID¶
The model must be a Bedrock model identifier. Bedrock hosts Anthropic Claude,
Amazon Nova, Meta Llama, Mistral, Cohere, OpenAI GPT and others. Each has
Bedrock's own ID: openai.gpt-6-sol, not OpenAI's gpt-6-sol. Bedrock rejects
an ID written for another provider's API with
The provided model identifier is invalid. Use one of the Claude IDs below:
| Model | Inference-profile ID (recommended) | Base model ID |
|---|---|---|
| Claude Opus 5.5 | us.anthropic.claude-opus-5-5 |
anthropic.claude-opus-5-5 |
| Claude Sonnet 4.6 | us.anthropic.claude-sonnet-4-6 |
anthropic.claude-sonnet-4-6 |
| Claude Haiku 4.5 | us.anthropic.claude-haiku-4-5-20251001-v1:0 |
anthropic.claude-haiku-4-5-20251001-v1:0 |
Haiku 4.5 keeps its dated -20251001-v1:0 suffix on Bedrock. Unlike Opus 5.5
and Sonnet 4.6, it has no undated anthropic.claude-haiku-4-5 ID.
On Bedrock, Claude Opus 4.7 and later (Opus 5.5 included) take lgtmaybe's findings schema as a forced tool call rather than native structured output. A forced tool call makes the model answer by calling one tool whose input is the schema. There is nothing to configure: litellm picks that route from its model map.
Prefer the inference-profile form. Most current Claude models on Bedrock can
only be invoked through a cross-region inference profile. They cannot run with
on-demand throughput on the bare model ID. So a bare ID often fails with the
same invalid model identifier (or on-demand throughput isn't supported)
error.
Prefix the ID with the geography that matches aws_region:
us.(US);eu.(Europe);apac.(Asia Pacific);- or the narrower
au./jp..
A global. prefix lets Bedrock route the call to any commercial region. Use the
bare anthropic.… ID only where on-demand access to that model is enabled in
your region.
Running Locally with Ambient AWS Credentials¶
If your shell has ambient AWS credentials (from
~/.aws, SSO, or an assumed role), you can review your current branch with
Bedrock. Bedrock request signing needs boto3. So install the bedrock extra
(the Action image already bundles it):
pip install 'lgtmaybe[bedrock]'
lgtmaybe review \
--provider bedrock \
--model us.anthropic.claude-sonnet-4-6
Bedrock always uses the AWS credential chain. lgtmaybe ignores --api-key for
it.
No matching distribution found for lgtmaybe[bedrock](from versions: none): this is not a packaging problem, because thebedrockextra is published on PyPI. It meanspipfound no version compatible with your environment. The usual cause is a Python older than the required 3.11. Check withpython --version, and install under 3.11 or later (e.g. withpipxoruv). If your Python is fine,pipcan't reach PyPI. Check your network, proxy or--index-url.
Troubleshooting¶
ExpiredTokenException: the OIDC exchange failed or the role session
expired. Check that the workflow grants id-token: write and that the IAM trust
policy names the correct GitHub repository.
AccessDeniedException: the role lacks bedrock:InvokeModel for the
selected model, or the model is not enabled in the Bedrock console for your
account and region. For an inference-profile ID (us./eu./apac.-prefixed),
the policy must allow the inference-profile/* ARN as well as
foundation-model/*.
The provided model identifier is invalid: the model is not a Bedrock
model ID. It is one of two things:
- Another provider's name, such as
gpt-6-solorclaude-sonnet-4-6. Bedrock's areopenai.gpt-6-solandus.anthropic.claude-sonnet-4-6. Pick an ID from the table above. - A model that needs a cross-region inference profile. Prefix it with
us./eu./apac.(e.g.us.anthropic.claude-sonnet-4-6) instead of using the bareanthropic.…ID.
output_config.format: Extra inputs are not permitted, outputConfig,
or a toolConfig / "doesn't support tool choice" refusal: the model's Converse
route (Bedrock's chat API) doesn't accept the structured-output shape lgtmaybe
sent. Bedrock rejected the request with a 400.
No action is needed. lgtmaybe re-sends the same schema as a forced tool call (which Converse does implement). The rest of the lens fan-out uses that shape straight away. Only a route that refuses the tool call too falls back to prompt-instructed JSON, which the lenient parser still reads. lgtmaybe logs that fallback as a warning naming the model and the reason.
If every lens fails at once with every review call failed on an older
lgtmaybe, upgrade, or pass --no-structured-output (structured_output: false)
as a stopgap.
Structured output stepped down after an odd reply: an empty reply, or one that parsed to something other than findings, moves the model one rung down to the forced tool call and keeps the schema. Only a second such reply gives the schema up. The provider log says which happened:
structured output stepped down to a forced tool callis the middle rung and costs nothing.structured output disabled for this modelis the floor. It carries areasonworth reading, such asrejected,empty-response,unparseable-output,tool-rejectedorinternal-tool-rejected.