AWS Builder Center
GitHub issues to PRs with AgentCore

GitHub issues to PRs with AgentCore

Move a card to In Progress and get a tested pull request back, with no AWS keys in GitHub and no idle compute.

Solution hosted on Agentcore Central solution library 
This post shows a working coding agent that turns a GitHub issue into a tested pull request. Move an issue titled agent-* to In Progress on a project board, and an Amazon Bedrock AgentCore Runtime session runs Claude Code on Amazon Bedrock to plan the work, implement and test it, and open a pull request. All code is in the sample repository .
What makes it practical:
  • Review by comment. Reply /agent fix <what failed> on the pull request, and a second automation pushes the fix to the same branch.
  • No stored keys. GitHub signs in to AWS with OIDC, and Claude is called through IAM, so there are no AWS keys in GitHub and no model API key anywhere.
  • No idle compute. Each job gets its own AgentCore session, which stops when the job ends.
  • Code checks the model's work. The orchestrator validates every plan, runs every acceptance test itself, and rejects any test that still passes without the code change.

Architecture

Architecture: GitHub Actions signs in to AWS with OIDC; a dispatcher Lambda reads the GitHub App key, mints a single-repo token, and starts an AgentCore Runtime session that runs Claude Code on Amazon Bedrock; CodeBuild builds the image into ECR
Figure 1: Architecture. Blue (B1–B4): how the agent's container image reaches the runtime. Orange (1–7): a new issue becomes a pull request. Dashed purple: a /agent fix comment joins the same path at step 2.
The container gets to the runtime in four steps. You run deploy.sh, which creates the ECR repository and the CodeBuild project, then starts a build (B1). CodeBuild clones the repository's main branch (B2), builds the linux/arm64 image natively from the Dockerfile using buildspec.yml, and pushes it to Amazon ECR tagged :latest and with the commit SHA (B3). deploy.sh then reads the image digest from ECR and runs terraform apply, which points the runtime at that digest; the runtime's execution role pulls it (B4).
ComponentSourceRole
Kickoff workflowagentcore-agent-in-progress.yml , kick_agentcore_agent.py Polls the board on a schedule (or runs on a label or manual trigger), claims the issue with a comment, signs in to AWS, dispatches the job
Fix workflowagentcore-agent-pr-feedback.yml , kick_agentcore_fix.py Dispatches /agent fix comments from trusted reviewers
Dispatcher Lambdadispatcher.py Mints a GitHub token scoped to one repository, then starts one AgentCore session per issue or fix comment
Runtime entrypoint and jobsagent.py , jobs.py Runs the job in the background, pushes, opens or updates the PR, stops the session
Orchestratororchestrator.py , plan.py Requirements, a plan validated in code, then implement, test, and commit each sub-task
Claude runnerclaude_runner.py One guarded Claude Code session per step, through the Claude Agent SDK
GitHub identitygithub_app.py , git_pr.py Used by the dispatcher to turn the GitHub App key into a 1-hour, single-repo token; the runtime only receives the token
Infrastructureterraform/ , deploy.sh , Dockerfile , buildspec.yml, codebuild.tfIAM roles, Lambda, runtime, and secret; arm64 image built in AWS CodeBuild

How it works

From issue to pull request

Figure 2. The project board is the trigger. Cards in Done link to the pull requests the agent opened.
Control flow for one issue: kickoff in GitHub Actions, the dispatcher mints a single-repo GitHub token and starts the AgentCore session, the agent plans, implements and tests each sub-task, then opens the pull request
Figure 3. Control flow for one issue.
Kickoff (about 20 seconds once the job starts). The workflow posts a claim comment before dispatching, so a later poll never starts the same issue twice. It invokes the dispatcher with InvocationType=Event, which returns HTTP 202 instead of waiting for a run that takes minutes. The poll is scheduled every 5 minutes, but GitHub runs scheduled workflows on a best-effort basis, so runs can be hours apart. To start right away, add an in-progress, agent, or agent-run label to the issue, or run the workflow manually (Step 9).
Agent run (minutes). The runtime acknowledges at once and works on a background thread. It clones the repository with the token the dispatcher passed in, runs the issue's verify commands as a baseline, and drives Claude Code through requirements, a validated plan, and an implement, test, and commit cycle for each sub-task.
Deliver. The runtime pushes agentcore/<slug>-<n>, opens a pull request with a requirements, sub-task, and test report (a draft if anything failed), and stops its session. PR #23  and PR #29  are examples. PR #37  was written by the agent too: from issue #36, it added the test check and single-repo token described below.

Iterate with /agent fix

Figure 4. A reviewer comment becomes new commits on the same branch, plus a reply with a report.
When the first pull request isn't quite right, comment on it:
1
2
/agent fix
pytest tests/test_vocabulary.py fails at import: use the analyzer stub, not the real one
The fix workflow reacts within seconds of the comment, and a scheduled rescan (best effort, like the board poll) picks up any comment it missed. Because the comment becomes model input, it accepts the command only from the linked issue's author or a collaborator with write access, and only on an open agentcore/* pull request. The runtime then reads the comment, inline review comments, the linked issue, and the changed files, and runs the same plan-and-test loop on the same branch. Finally it pushes the commits and replies with a report. Each commit carries an Agent-Fix-Comment: <id> trailer, so a replayed comment does nothing. Repeat until the pull request is right; see run_fix_job in jobs.py .

Inside the code

Dispatcher: one token and one session per job

Source: dispatcher.py . Before starting the runtime, the dispatcher reads the GitHub App key and mints a 1-hour token scoped to this repository, which it passes as github_token. If minting fails, it logs only the error type and returns 502, and the runtime never starts. The session ID is derived from the issue or comment, so a duplicate event lands in the same session instead of starting a second one. Lambda's async retries are also turned off in main.tf .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def mint_token(repo: str) -> str:
"""Short-lived GitHub App installation token scoped to ``repo`` alone."""
from github_app import get_installation_token # packaged next to the handler
return get_installation_token(repository=repo)

def handler(event, context):
... # validate mode + required fields; only titles starting with "agent-"
try:
github_token = mint_token(event["repo"]) # reads the App key from Secrets Manager
except Exception as exc:
logger.error("mint_token failed: %s", type(exc).__name__) # error type only, never the message
return _response(502, error="could not mint GitHub token") # runtime never starts
resp = boto3.client("bedrock-agentcore").invoke_agent_runtime(
agentRuntimeArn=agent_runtime_arn,
runtimeSessionId=session_id_for(event), # a replay lands in the same session
payload=json.dumps({**event, "runtime_arn": agent_runtime_arn,
"github_token": github_token}).encode("utf-8"),
contentType="application/json", accept="application/json",
)

Runtime entrypoint: return now, work in the background

Source: agent.py . BedrockAgentCoreApp serves the AgentCore Runtime contract. The registered async task keeps /ping at HealthyBusy while the job runs, and the session stops itself when the job finishes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from bedrock_agentcore.runtime import BedrockAgentCoreApp

app = BedrockAgentCoreApp() # serves POST /invocations + GET /ping on :8080

@app.entrypoint
def invoke(payload, context):
return handle(payload, context.session_id,
add_task=lambda name: app.add_async_task(name), # /ping -> HealthyBusy
complete_task=app.complete_async_task)

def handle(payload, session_id, *, add_task, complete_task, job=run_job, stop=stop_session):
payload = validate(payload)
key = job_key(payload) # "issue:<repo>#12" or "fix:<repo>#25:<comment>"
with _lock:
if key in _running:
return {"status": "already_running", "job": key}
_running.add(key)
task = add_task(key)

def body():
try:
job(payload) # clone -> orchestrate -> push -> PR
finally:
with _lock:
_running.discard(key)
idle = not _running
complete_task(task)
if idle: # StopRuntimeSession, no idle compute
stop(payload.get("runtime_arn"), session_id)

threading.Thread(target=body, name=key, daemon=True).start()
return {"status": "accepted", "job": key} # the dispatcher returns in seconds

if __name__ == "__main__":
app.run()

Claude Code on Amazon Bedrock

Source: claude_runner.py . Setting CLAUDE_CODE_USE_BEDROCK=1 routes inference through the runtime's IAM role. Every step is a fresh session with its own tools, turn limit, and $3 budget. A PreToolUse hook (check_tool) blocks edits outside the sub-task's files and limits Bash to an allowlist. The CLI still uses the role to call Bedrock, but CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 keeps those credentials out of the Bash and python commands it runs.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def bedrock_env(model_id=None) -> dict:
"""Environment for the Claude Code CLI bundled in the Claude Agent SDK."""
model = model_id or os.environ.get("BEDROCK_MODEL_ID") # e.g. global.anthropic.claude-sonnet-5-5
return {
"CLAUDE_CODE_USE_BEDROCK": "1", # use Bedrock via the runtime's IAM role
"AWS_REGION": os.environ.get("AWS_REGION", "us-east-1"),
"ANTHROPIC_MODEL": model,
"ANTHROPIC_SMALL_FAST_MODEL": model, # one model -> one IAM grant
"DISABLE_TELEMETRY": "1",
"DISABLE_AUTOUPDATER": "1",
}

# ClaudeRunner.__init__: the CLI needs AWS credentials for Bedrock, but the
# Bash/python it spawns runs repo code. Set last so extra_env cannot unset it.
self.env = {**bedrock_env(model_id), **(extra_env or {}),
"CLAUDE_CODE_SUBPROCESS_ENV_SCRUB": "1"}

options = ClaudeAgentOptions(
cwd=cwd,
env=self.env,
system_prompt={"type": "preset", "preset": "claude_code", "append": system_append},
allowed_tools=READ_TOOLS + (WRITE_TOOLS if write_paths is not None else []),
disallowed_tools=["WebFetch", "WebSearch", "Task", "Agent", "NotebookEdit"],
permission_mode="dontAsk", # anything not allowed is refused
hooks={"PreToolUse": [HookMatcher(hooks=[guard])]},
setting_sources=["project"],
max_turns=max_turns,
max_budget_usd=max_budget_usd, # $3 per session
output_format={"type": "json_schema", "schema": schema} if schema else None,
)
async for message in query(prompt=prompt, options=options):
if isinstance(message, ResultMessage):
final = message # structured_output, total_cost_usd, ...

Plans are checked in code

Source: plan.py . Every requirement must be covered, each sub-task changes 1–4 files and has a runnable pytest or unittest acceptance command (py_compile is allowed only for the overall verify step), and dependencies only point backwards. A rejected plan goes back to Claude with the violations, for up to 3 attempts.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def validate_plan(subtasks, requirement_ids, tree) -> list[str]:
"""Return every rule the plan breaks (empty list == acceptable)."""
v = []
position = {s.id: i for i, s in enumerate(subtasks)}
for i, s in enumerate(subtasks):
if not s.write_paths:
v.append(f"{s.id} changes no files; fold inspection into planning")
if len(s.write_paths) > MAX_FILES_PER_SUBTASK: # 4
v.append(f"{s.id} touches {len(s.write_paths)} files; split it")
if not s.covers:
v.append(f"{s.id} covers no requirement")
for d in s.depends_on:
if d not in position or position[d] >= i:
v.append(f"{s.id} depends on {d}, which is not an EARLIER sub-task")
if problem := command_violation(s.acceptance, tree | set(s.new_files)):
v.append(f"{s.id} acceptance: {problem}") # must be runnable
missing = [r for r in requirement_ids if r not in {c for s in subtasks for c in s.covers}]
if missing:
v.append(f"requirements {missing} are not covered by any sub-task")
return v

Tests must prove the change

Source: orchestrator.py . After an acceptance command passes, the orchestrator stashes the sub-task's non-test changes and runs it again. If it still passes, the test doesn't exercise the change, so the sub-task is rejected, retried, and then re-planned. Test-only sub-tasks skip this check.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def _exercises_change(self, task: SubTask) -> bool:
"""Fail-before check: acceptance must FAIL with the non-test changes stashed."""
code = [p for p in self._changed_paths() if not _is_test_path(p)]
if not code:
return True # test-only sub-task: nothing to stash
before = self._git("stash", "list")
self._git("stash", "push", "-u", "-q", "--", *code)
stashed = self._git("stash", "list") != before
try:
return not self.run_command(task.acceptance).ok
finally:
if stashed:
self._git("stash", "pop", "-q")

# in _execute(), after the acceptance command passes with the full change:
if check.ok and not self._exercises_change(task):
check = CommandResult(task.acceptance, False,
"The acceptance command passes even without your code change: "
"test does not exercise the change. Write a test that FAILS "
"without the change and passes with it.")
Acceptance commands and python inside a session run the repository's own code, so they get an environment with AWS credentials and GitHub tokens removed (scrub_env in orchestrator.py), and the runtime role can't read the GitHub App key. Review agent pull requests like any other contribution.

The GitHub–AWS handshake

Credentials and trust: steps A to G are one-time setup; steps 1 to 7 run automatically. The dispatcher Lambda alone reads the GitHub App key and passes a single-repo token to the AgentCore Runtime, which has no Secrets Manager access
Figure 5. Letters A–G are one-time setup you do; numbers 1–7 happen automatically on every run.
GitHub to AWS. Each workflow job requests a GitHub OIDC token (id-token: write). AWS STS validates it against the IAM OIDC provider and the role's trust policy (aud and sub), then returns temporary credentials that can only invoke the dispatcher.
AWS to GitHub. Only the dispatcher Lambda's role can read the GitHub App key from AWS Secrets Manager. For each job, it signs a 9-minute App JWT and exchanges it for a 1-hour installation token scoped to this one repository, then passes the token to the runtime in the invoke payload. The runtime role has no Secrets Manager access. Only orchestrator code uses the token, for API calls, clone, and push (git_pr.py ); it never enters a Claude session. The runtime role calls Bedrock, so there is no model API key.
CredentialWhoWhereLifetime
IAM OIDC identity providerYou (A)AWS IAMOne per account
github_oidc_sub trust conditionYou (B)terraform.tfvarsUntil changed
GitHub App ID, installation ID, private keyYou (C, D)Created in GitHub; stored in Secrets Manager <prefix>/github-appreadable only by the dispatcherUntil rotated
AWS_ACCOUNT_ID and optional overridesYou (E)Repository Actions variablesNot a secret
GH_PROJECT_TOKEN (fine-grained PAT, Projects read)You (F)Repository Actions secretExpiry you set
Bedrock model accessYou (G)Amazon Bedrock consoleAccount setting
OIDC token, then temporary AWS credentialsAutomatic (1–2)Issued per jobMinutes, then up to 1 hour
GITHUB_TOKENAutomaticIssued per jobOne job
App JWT, then single-repo installation tokenAutomatic (4)Minted by the dispatcher; passed to the runtime in the invoke payload9 minutes, then 1 hour
The trust policy, from main.tf :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
data "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
}

resource "aws_iam_role" "github_actions_dispatch" {
name = "${var.name_prefix}-github-oidc"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Federated = data.aws_iam_openid_connect_provider.github.arn }
Action = ["sts:AssumeRoleWithWebIdentity", "sts:TagSession"]
Condition = {
StringEquals = { "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" }
StringLike = { "token.actions.githubusercontent.com:sub" = var.github_oidc_sub }
}
}]
})
}

resource "aws_iam_role_policy" "github_actions_invoke_dispatcher" {
role = aws_iam_role.github_actions_dispatch.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{ Effect = "Allow", Action = ["lambda:InvokeFunction"],
Resource = aws_lambda_function.dispatcher.arn }]
})
}
Get sub exactly right; it is the most common failure. In this repository the token names the repository in numeric form (repo:<owner>@<owner_id>/<repo>@<repo_id>:...), and a policy written with the plain repo:<owner>/<repo> form was denied. Print the claim once instead of guessing:
1
2
3
4
5
6
7
8
# Temporary step: print ONLY the sub claim of this job's OIDC token
- name: Show OIDC sub claim
uses: actions/github-script@v7
with:
script: |
const t = await core.getIDToken('sts.amazonaws.com');
const claims = JSON.parse(Buffer.from(t.split('.')[1], 'base64url').toString());
core.info(`sub=${claims.sub}`);
The token exchange in github_app.py , which the dispatcher calls with the event's repository:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
SCOPED_PERMISSIONS = {"contents": "write", "pull_requests": "write", "issues": "write"}

def mint_app_jwt(creds, now=None) -> str:
"""Short-lived RS256 JWT that authenticates AS the GitHub App."""
now = int(now if now is not None else time.time())
payload = {"iat": now - 60, # tolerate clock skew
"exp": now + 9 * 60, # GitHub allows up to 10 minutes
"iss": creds.app_id}
return jwt.encode(payload, creds.private_key, algorithm="RS256")

def get_installation_token(creds=None, repository=None) -> str:
"""1-hour installation token; pass repository='owner/name' to scope it to that repo."""
creds = creds or load_credentials() # Secrets Manager: GITHUB_APP_SECRET_ARN (dispatcher only)
kwargs = {}
if repository:
kwargs["json"] = {"repositories": [repository.split("/")[-1]],
"permissions": dict(SCOPED_PERMISSIONS)}
resp = requests.post(
f"https://api.github.com/app/installations/{creds.installation_id}/access_tokens",
headers={"Authorization": f"Bearer {mint_app_jwt(creds)}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28"},
timeout=REQUEST_TIMEOUT, **kwargs)
if resp.status_code != 201:
raise RuntimeError(f"Failed to mint installation token: {resp.status_code}")
return resp.json()["token"]
Figure 6. The handshake in practice: one kickoff job signs in through OIDC and dispatches in 18 seconds, with no stored AWS keys.

Set it up for your repository

You need an AWS account in a Region with AgentCore Runtime and your Claude model (us-east-1 by default); the AWS CLI, Terraform (hashicorp/aws v6), Python 3 with pip, jq, and the GitHub CLI; and a GitHub Projects board with an In Progress status. You do not need Docker; the image is built in AWS CodeBuild. The letters match Figure 5.
Step 1. Copy the files. Copy .github/agentcore/ , the two kick_agentcore_*.py scripts, and the two agentcore-agent-*.yml workflows. In the in-progress workflow, set PROJECT_OWNER, PROJECT_OWNER_TYPE, PROJECT_NUMBER, and DEFAULT_BASE_BRANCH.
Step 2 (A). Trust GitHub's OIDC issuer, once per account:
1
2
3
4
5
6
7
# Does the account already trust GitHub's OIDC issuer?
aws iam list-open-id-connect-providers

# If not, create it once per account
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com
Step 3 (B). Configure and deploy. Run the debug step above to get your sub, or build it from gh api users/<owner> --jq .id and gh api repos/<owner>/<repo> --jq .id. Then create an uncommitted terraform.tfvars and run deploy.sh :
1
2
3
4
5
6
# .github/agentcore/terraform/terraform.tfvars (do not commit)
github_repo = "<owner>/<repo>"
github_oidc_sub = "repo:<owner>@<owner_id>/<repo>@<repo_id>:*" # exactly what the debug step printed, with :*
aws_region = "us-east-1"
name_prefix = "agentcore-decomposer"
agent_budget_usd = 20
1
2
3
4
5
6
7
8
9
10
cd .github/agentcore/terraform
../deploy.sh <ACCOUNT_ID> us-east-1

# Stage 0 create the ECR repository if it does not exist
# Layer pip install lambda/requirements.txt -> build/dispatcher_layer (x86_64 wheels)
# Stage 1 terraform apply -target=<CodeBuild project + role>
# Stage 2 aws codebuild start-build -> builds linux/arm64 in AWS (no local Docker)
# Stage 3 terraform apply -var agent_container_image_uri=<ecr>@sha256:<digest>
terraform output # agent_runtime_arn, dispatcher_lambda_name, github_actions_role_arn,
# github_app_secret_arn
Step 4 (C). Create the GitHub App. In Settings > Developer settings > GitHub Apps, create an App with Contents, Pull requests, and Issues set to Read and write, with the webhook turned off. Note the App ID, generate a private key, and install the App on this repository only. The installation page URL ends in the installation ID.
Step 5 (D). Store the App credentials. Writing the secret with the CLI keeps the key out of Terraform state:
1
2
3
4
5
6
7
8
9
aws secretsmanager put-secret-value \
--secret-id agentcore-decomposer/github-app \
--secret-string "$(jq -n \
--arg app_id '<APP_ID>' \
--arg installation_id '<INSTALLATION_ID>' \
--rawfile private_key ./<your-app>.private-key.pem \
'{app_id: $app_id, installation_id: $installation_id, private_key: $private_key}')
"


# then remove the local .pem (or move it to your normal key vault)
Step 6 (E, F). Configure the repository:
1
2
3
4
5
6
7
gh variable set AWS_ACCOUNT_ID --repo <owner>/<repo> --body <ACCOUNT_ID>
# optional overrides (defaults shown)
gh variable set AWS_REGION --repo <owner>/<repo> --body us-east-1
gh variable set AGENTCORE_OIDC_ROLE_NAME --repo <owner>/<repo> --body agentcore-decomposer-github-oidc
gh variable set DISPATCHER_LAMBDA_NAME --repo <owner>/<repo> --body agentcore-decomposer-dispatcher

gh secret set GH_PROJECT_TOKEN --repo <owner>/<repo> # paste the fine-grained PAT
Step 7 (G). Enable model access for your Claude model in the Amazon Bedrock console, if your account requires it.
Step 8. Adapt it to your project. List your test dependencies in requirements-test-env.txt . Acceptance commands support pytest and unittest (py_compile only for the overall verify step), and a sub-task that changes behavior must include a test that fails without the change. For other languages, extend the Dockerfile, the Bash allowlist in claude_runner.py, and the command check in plan.py. Claude Code also reads your CLAUDE.md, so documented conventions apply to every session.
Step 9. Run your first issue. Clear "Done when" criteria give the best plans:
1
2
3
4
5
6
7
8
9
# 1. create an issue whose title starts with agent-
gh issue create --repo <owner>/<repo> \
--title "agent-add-greeting-helper" \
--body "Add utils/greeting.py with greet(name) returning 'Hello, {name}!'.
Done when: pytest tests/test_greeting.py passes."


# 2. add it to the project board and move it to In Progress
# (or start it immediately without waiting for the 5-minute poll)
gh workflow run agentcore-agent-in-progress.yml --repo <owner>/<repo> -f issue_number=<N>

Upgrading an existing deployment

Pull the latest code and re-run deploy.sh. It builds the dispatcher's dependency layer, rebuilds the image, and applies Terraform, which moves Secrets Manager access from the runtime role to the dispatcher.

Troubleshooting

SymptomFix
AssumeRoleWithWebIdentity deniedPrint the sub claim and set github_oidc_sub to it; confirm the OIDC provider exists and sts:TagSession is allowed
Kickoff times out after 60 secondsKeep InvocationType=Event for the dispatcher invoke
One issue starts several runsKeep Lambda retries at 0 and the deterministic session ID
Bedrock AccessDeniedExceptionEnable model access and check the model ARNs in the runtime role
Issue never picked upThe title must start with agent-, and the issue must be open, in In Progress, and without an earlier kickoff comment
Dispatcher returns 502 "could not mint GitHub token"Check the dispatcher log for "mint_token failed: <ErrorType>". If it's ImportError, re-run deploy.sh. Otherwise, fill the App secret and confirm the App is installed on the repository.
Sub-task rejected: "test does not exercise the change"Its test passes without the code change; give the issue a testable "Done when"

Clean up

1
2
3
4
5
cd .github/agentcore/terraform
terraform destroy -var aws_account_id=<ACCOUNT_ID>

# created by deploy.sh outside Terraform
aws ecr delete-repository --repository-name agentcore-decomposer --force
Then uninstall the GitHub App, revoke the personal access token, and remove the workflows.

Wrapping up

A workflow that signs in through OIDC, a Lambda that dispatches asynchronously, one AgentCore session per job, and an orchestrator that keeps Claude Code inside code-enforced guardrails are enough to turn a card on a board into a reviewed pull request. Start with a small, well-specified agent-* issue in the sample repository , then use /agent fix to iterate.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article