Skip to content

Review with AWS Bedrock (keyless OIDC)

Use this guide to run lgtmaybe with AWS Bedrock using GitHub's OIDC token — no static AWS credentials stored in secrets.

Contents

How it works

GitHub Actions issues a short-lived OIDC token. AWS STS exchanges that token for temporary IAM credentials scoped to a role you control. The action performs that exchange for you (pass aws_role_arn) and lgtmaybe picks up the ambient credentials automatically — no AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY in your secrets.

One-time AWS setup

This is the human-only part — do it once in your AWS account:

  1. Create an IAM OIDC identity provider for token.actions.githubusercontent.com.
  2. Create an IAM role with a trust policy scoped to your repo (repo:<org>/lgtmaybe:*).
  3. Attach the least-privilege policy below.
  4. Confirm the models you want are enabled in the target region (model access request in the Bedrock console).
  5. Note the role ARN (e.g. arn:aws:iam::123456789012:role/lgtmaybe-bedrock) — it becomes the aws_role_arn action input. No static key is ever stored.

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 fans the call out 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 of them still fails with AccessDeniedException. A bare anthropic.… model id needs only the foundation-model/* ARN, but most current Claude models are invocable only through an inference profile — see the model table below.

Scope each Resource to specific model / inference-profile ARNs for tighter least-privilege once it works, e.g. arn:aws:bedrock:*:<account-id>:inference-profile/us.anthropic.claude-opus-4-8* and arn:aws:bedrock:*::foundation-model/anthropic.claude-opus-4-8*.

Workflow example

The action assumes the role for you — no separate configure-aws-credentials step needed. Store the role ARN in an AWS_ROLE_ARN secret.

name: lgtmaybe

on:
  pull_request_target:
  issue_comment:
    types: [created]

permissions:
  id-token: write          # required for the OIDC token exchange (keyless)
  pull-requests: write     # required to post review comments
  contents: read

jobs:
  review:
    # Only trusted authors (repo owner / org member / collaborator) can trigger
    # a review, so fork PRs and drive-by comments cannot spend your provider
    # budget. A maintainer can still opt in to an external PR with /review.
    if: >-
      (github.event_name == 'pull_request_target' &&
       contains(fromJson('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association)) ||
      (github.event.issue.pull_request &&
       contains(fromJson('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) &&
       (contains(github.event.comment.body, '/review') ||
        contains(github.event.comment.body, '/improve') ||
        contains(github.event.comment.body, '/ask') ||
        contains(github.event.comment.body, '/describe') ||
        contains(github.event.comment.body, '/diagram')))
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - 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

Choosing a Bedrock model ID

The model must be a Bedrock model identifier. Bedrock hosts Anthropic Claude, Amazon Nova/Titan, Meta Llama, Mistral, Cohere and others — it does not host OpenAI's GPT models, so an id like openai.gpt-5.5 is rejected with The provided model identifier is invalid. Use one of the Claude ids below (prefixed with a cross-region inference profile — see the note):

Model Inference-profile ID (recommended) Base model ID
Claude Opus 4.8 us.anthropic.claude-opus-4-8 anthropic.claude-opus-4-8
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 anthropic.claude-haiku-4-5

Prefer the inference-profile form. Most current Claude models on Bedrock are invocable only through a cross-region inference profile, not via 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 with your geography: us. (US), eu. (Europe) or apac. (Asia Pacific), matching aws_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 local shell has AWS credentials (via ~/.aws, SSO, or an assumed role), you can review your current branch's changes with Bedrock. Bedrock signing needs boto3, so install the extra (the Action image already bundles it):

pip install 'lgtmaybe[bedrock]'

lgtmaybe review \
  --provider bedrock \
  --model us.anthropic.claude-sonnet-4-6

lgtmaybe does not require or accept a static API key for Bedrock.

No matching distribution found for lgtmaybe[bedrock] (from versions: none) — this is not a packaging problem; the bedrock extra is published on PyPI. It means pip found no version compatible with your environment. The usual cause is an interpreter older than the required Python 3.11+ (check with python --version); install under 3.11+ (e.g. via pipx/uv). If your Python is fine, your pip can't reach PyPI — check your network/proxy or --index-url.

Troubleshooting

ExpiredTokenException — the OIDC exchange failed or the role session expired. Check that id-token: write permission is present in the workflow and that the IAM trust policy references 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 also allow the inference-profile/* ARN, not just foundation-model/*.

output_config.format: Extra inputs are not permitted, outputConfig, or a toolConfig / "doesn't support tool choice" refusal — the model's Converse route doesn't accept the structured-output shape lgtmaybe asks for, and Bedrock rejects the whole request with a 400. No action is needed: enforcement degrades one rung at a time rather than being switched off. A rejected response_format is re-sent as the same schema through the mechanism Converse does implement — a forced tool call, whose arguments are the findings — and the rest of the lens fan-out uses that shape straight away. Only a route that refuses the tool too falls back to prompt-instructed JSON (the prompt asks for JSON anyway and the parser is lenient); that fallback is logged as a warning naming the model and the reason. If you're on a build from before this (the symptom is every lens failing at once, and the review reporting every review call failed), upgrade, or pass --no-structured-output / set structured_output: false as a stopgap.

Every review comes back unstructured after one odd reply — an empty reply, or one that parsed to something that wasn't findings, is evidence against the mechanism in play, not against enforcement. It now steps the model down to the forced tool call and keeps the schema; only a second such reply gives it up. On a build from before this, one blip on one lens cost enforcement for every later call of the run. The provider log names what happened: structured output stepped down to a forced tool call is the middle rung and costs you nothing, while structured output disabled for this model is the floor and carries a reason (empty-response, unparseable-output, tool-rejected, internal-tool-rejected) worth reading.

The provided model identifier is invalid — the model is not a Bedrock model id. Two common causes: (1) it's a non-Bedrock id such as openai.gpt-5.5 or another provider's name — Bedrock hosts Claude / Nova / Llama / Mistral / Cohere, not OpenAI GPT, so pick a Claude id from the table above; (2) the model needs a cross-region inference profile — prefix with us./eu./apac. (e.g. us.anthropic.claude-sonnet-4-6) rather than the bare anthropic.… id.