The squad lead opens a PR, the CI build is green, and CodeCommit cheerfully shows a “Merge” button with no further checks. Anyone with codecommit:MergePullRequest* can land it. There’s no equivalent of GitHub’s “require two reviewers” until you attach an approval rule template — and unlike GitHub branch protection, this is a separate AWS API surface with its own JSON schema and its own scoping model. The rules are powerful (you can require approvers from specific IAM principals, you can require CI status, you can require N approvers from a pool) but they’re invisible until configured, and the team usually doesn’t know they exist.
What’s happening
An approval rule template is a JSON document that defines the conditions a PR must satisfy before merge. The template includes a DestinationReferences filter (which branches the rule applies to), an Approvers block (which IAM principals can satisfy the approval, and how many are required), and optionally a CodeBuild status check.
Templates are decoupled from repos — you create the template once, then associate it with each repo (or with multiple repos at once). Associating a template with a repo applies it to all PRs in that repo that target one of the destination refs. Disassociating removes future enforcement; in-flight PRs keep the rule.
The CodeBuild integration is the part teams underuse. The template can reference a CodeBuild project ARN; the PR must have a successful build of that project against the source ref before the approval rule will allow merge. This gives you a CI gate that’s enforced at the merge boundary, not by convention.
The scoping model is repo-by-repo by default, but the CLI and SDK accept multiple repo names in one association call. For a fleet, the right pattern is to associate the template programmatically against every repo carrying a specific tag — same pattern as the IAM policy scoping.
How to verify
Check whether any approval rule templates exist in the account:
aws codecommit list-approval-rule-templates \
--query 'approvalRuleTemplateNames' \
--output table
If the list is empty, no PR in the account is gated. That’s the common starting state on a CodeCommit org that grew organically.
For a specific repo, see which templates are associated:
aws codecommit list-associated-approval-rule-templates-for-repository \
--repository-name payments-api \
--query 'approvalRuleTemplateNames' \
--output table
And to inspect an existing template’s content:
aws codecommit get-approval-rule-template \
--approval-rule-template-name "require-2-and-ci" \
--query 'approvalRuleTemplate.approvalRuleTemplateContent' \
--output text | jq .
For an open PR, check whether its approval rule has been satisfied:
aws codecommit evaluate-pull-request-approval-rules \
--pull-request-id 42 \
--revision-id $(aws codecommit get-pull-request \
--pull-request-id 42 \
--query 'pullRequest.revisionId' --output text) \
--query 'evaluation' \
--output json
The response includes approved: true/false and an overridden: true/false flag — overrides are the escape hatch and should be audited.
The fix
The reference pattern for a production-tier repo, with two approvers and a CodeBuild CI gate:
- Define the template content as JSON. The
Statements[].TypeisApprovers;NumberOfApprovalsNeededis the required count;ApprovalPoolMembersscopes who can satisfy the rule (IAM principal ARNs or a pattern likearn:aws:iam::123456789012:role/Engineer-*). TheDestinationReferencesarray lists protected refs; PRs targeting other refs are not subject to the rule.
{
"Version": "2018-11-08",
"DestinationReferences": ["refs/heads/main"],
"Statements": [
{
"Type": "Approvers",
"NumberOfApprovalsNeeded": 2,
"ApprovalPoolMembers": [
"arn:aws:sts::123456789012:assumed-role/Engineer-*/*"
]
}
]
}
- Create the template. The content goes in as an inline string; the CLI doesn’t accept
file://for this argument cleanly across versions, so the safer pattern is--cli-input-json:
cat > /tmp/template-input.json <<'EOF'
{
"approvalRuleTemplateName": "require-2-and-ci-prod",
"approvalRuleTemplateContent": "{\"Version\":\"2018-11-08\",\"DestinationReferences\":[\"refs/heads/main\"],\"Statements\":[{\"Type\":\"Approvers\",\"NumberOfApprovalsNeeded\":2,\"ApprovalPoolMembers\":[\"arn:aws:sts::123456789012:assumed-role/Engineer-*/*\"]}]}",
"approvalRuleTemplateDescription": "Production repos: 2 approvers, CI green"
}
EOF
aws codecommit create-approval-rule-template --cli-input-json file:///tmp/template-input.json
- Associate the template with every prod-tier repo. Loop over repos carrying
Tier=prod:
aws codecommit list-repositories --query 'repositories[].repositoryName' --output text | \
while read r; do
tier=$(aws codecommit list-tags-for-resource \
--resource-arn "arn:aws:codecommit:us-east-1:123456789012:$r" \
--query 'tags.Tier' --output text)
if [ "$tier" = "prod" ]; then
aws codecommit associate-approval-rule-template-with-repository \
--approval-rule-template-name "require-2-and-ci-prod" \
--repository-name "$r"
echo "Associated: $r"
fi
done
Same loop runs again every time a new repo is created. Wire it into the repo-creation script if you have one — a Terraform aws_codecommit_repository resource paired with aws_codecommit_approval_rule_template_association is the durable version.
- Add a CI gate via CodeBuild. Approval rule templates do not natively reference CodeBuild build status — the integration is event-driven. The pattern: when a PR is opened or updated, EventBridge fires a
CodeCommit Pull Request State Changeevent; a Lambda receives it, kicks off a CodeBuild project against the source ref, and posts the build status back as a PR comment on completion. The merge is gated by a separate approval that the build-status Lambda creates (a second approval rule template scoped to require a specific IAM role — the Lambda’s role — to approve).
The simplified Lambda approval flow:
import boto3
cc = boto3.client('codecommit')
def lambda_handler(event, _ctx):
pr_id = event['detail']['pullRequestId']
revision_id = event['detail']['revisionId']
build_status = event['detail']['buildStatus'] # SUCCEEDED | FAILED
if build_status == 'SUCCEEDED':
cc.update_pull_request_approval_state(
pullRequestId=pr_id,
revisionId=revision_id,
approvalState='APPROVE'
)
# On FAILED, leave unapproved; the developer must push a fix.
The Lambda’s execution role becomes one of the approvers; the approval rule template requires at least one approval from that role’s ARN. PR merges only when CI is green.
- Audit overrides. Anyone with
codecommit:OverridePullRequestApprovalRulescan bypass the rule on a specific PR. That action should appear in CloudTrail; pipeOverridePullRequestApprovalRulesevents into your SIEM and alert on them. Overrides for hotfixes are legitimate; overrides as a pattern are a sign the rule needs adjustment.
Two rules we hold:
Template once, associate many times. Maintaining one require-2-and-ci-prod template across 30 repos is sustainable. Maintaining 30 per-repo inline rules is not. Use the template + tag-scoped association pattern.
Don’t put the CI gate inside the approval rule itself. Approval rules in CodeCommit are simple by design — required approver counts. The CodeBuild status check belongs in an event-driven Lambda that toggles approval based on build outcome. Trying to make the rule itself “know” about CodeBuild leads to brittle workarounds.
The reference setup is reproducible in a Terraform module: template + association loop + EventBridge + Lambda. Once that module is in place, new repos in protected tiers inherit the gating by tag, with no per-repo configuration.
Stack Harbor runs CodePipeline + CodeCommit (and the GitHub-on-AWS variants) as part of Managed AWS onboarding and ongoing Managed Cloud Ops. If your CodeCommit PRs are merging without enforced review or CI status, start with an infrastructure consult.