Lambda "The Role Defined for the Function Cannot Be Assumed by Lambda": The Trust Policy, the 10-Second Timing Trap, and the Fix in the CLI, CloudFormation, Terraform, CDK and SAM
The role defined for the function cannot be assumed by Lambda. comes back from CreateFunction or UpdateFunctionConfiguration as an InvalidParameterValueException, and it means one of two things. Either the role's trust policy doesn't name lambda.amazonaws.com as a principal that may assume it, or the role is fine and you created it a few seconds ago: IAM is eventually consistent, and a role that exists in one API call can still be invisible to Lambda in the next. The second cause is the one that fools people, because the same script works on the second run. This page shows you how to tell the two apart in one command, the exact trust policy, the wait-and-retry that fixes the timing case, and the right fix in CloudFormation, Terraform, CDK and SAM, plus how this error differs from the iam:PassRole denial that lands on the same command.
Ethan, a developer, deploys the booking function for his friend Jake's phone repair shop with a script: create the role, attach the logging policy, create the function. On Tuesday it failed with this error. He ran it again, it worked, and he deleted the function to see if he'd imagined it. It failed again. Jake asked whether AWS was "flaky." Ethan had a better word by the end of the afternoon: eventual.
What the error means, in plain English
Every Lambda function has an execution role: the identity it wears while it runs. When Lambda runs your function, it assumes that role, which means it calls sts:AssumeRole on your behalf. A role will only let a principal assume it if the role's trust policy says so. So when you create a function and hand it a role ARN, Lambda immediately checks: can the Lambda service assume this role? If the answer is no, the function is not created, and you get:
An error occurred (InvalidParameterValueException) when calling the CreateFunction operation:
The role defined for the function cannot be assumed by Lambda.
In CloudFormation the same text arrives wrapped: Resource handler returned message: "The role defined for the function cannot be assumed by Lambda.", and the stack rolls back. In Terraform it's creating Lambda Function (...): operation error Lambda: CreateFunction, ... InvalidParameterValueException: The role defined for the function cannot be assumed by Lambda. Same check, same two causes.
The check can fail for exactly two reasons. The trust policy on the role really doesn't allow Lambda, or the trust policy does allow Lambda but IAM hasn't finished propagating the role (or a change to it) to every service yet. There is no third reason, which is good news: one command below tells you which one you have.
Jake: "So the role exists, and Lambda still says it can't use it?"
Ethan: "A role is a thing with a lock on it. The trust policy is the list of who holds a key. Either Lambda isn't on the list, or it is and the locksmith hasn't delivered the key yet. The second one takes ten seconds."
One command to tell the two causes apart
Read the trust policy of the role you passed, not the one you think you passed. Use the exact role name from the ARN in the failing command:
aws iam get-role --role-name booking-fn-role \
--query 'Role.AssumeRolePolicyDocument.Statement[].Principal' --output json
You want to see this, and only this, for a plain Lambda function:
[
{
"Service": "lambda.amazonaws.com"
}
]
If the output names a different service, such as ec2.amazonaws.com because the role was cloned from an instance role, or an account or user ARN, or nothing at all, you have cause one: fix the trust policy. If the output shows lambda.amazonaws.com exactly, you have cause two: timing, and the fix is to retry. Two more things to glance at in the same breath: the account ID inside the role ARN must be the account you're deploying to (aws sts get-caller-identity tells you which one that is), and the ARN must be a role ARN, arn:aws:iam::123456789012:role/name, not a user ARN and not an instance-profile ARN (arn:aws:iam::123456789012:instance-profile/name), which looks similar and is a different object.
| What get-role shows | Cause | Fix |
|---|---|---|
"Service": "lambda.amazonaws.com", role created or changed in the last minute | Propagation | Wait 10 seconds, retry up to six times |
"Service": "ec2.amazonaws.com" or another service | Trust policy copied from a different role type | Replace the trust policy |
"AWS": "arn:aws:iam::...:user/..." or an account ID, no Service line | A role built for people, not for Lambda | Add the Lambda service principal |
A Condition on aws:SourceArn or aws:SourceAccount | Condition copied from another service's example | Remove it, or make it match |
NoSuchEntity from get-role | Wrong account, wrong name, or the role was deleted | Check sts get-caller-identity and the ARN |
Message says iam:PassRole instead | Your own permission, not the trust policy | Allow iam:PassRole on the role for the deployer |
Cause one: the trust policy, and the exact JSON that works
A trust policy is a small JSON document attached to the role that says who may assume it. For Lambda, the principal is the service itself. This is the whole file:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
Save it as trust.json. To fix an existing role in place, replace its trust policy; nothing else about the role changes, and functions that already use it keep working:
aws iam update-assume-role-policy --role-name booking-fn-role --policy-document file://trust.json
aws iam get-role --role-name booking-fn-role \
--query 'Role.AssumeRolePolicyDocument.Statement[].Principal.Service' --output text
# lambda.amazonaws.com
To create a role correctly from the start, give the trust policy at creation and attach the basic logging policy, which is what every function needs to write to CloudWatch:
aws iam create-role --role-name booking-fn-role \
--assume-role-policy-document file://trust.json
aws iam attach-role-policy --role-name booking-fn-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
The mistakes that produce a wrong trust policy, in the order we see them: a role copied from an EC2 or ECS example with its principal left as ec2.amazonaws.com or ecs-tasks.amazonaws.com; a trust policy that names an AWS account or a user, which is how roles for people are built, where the service line was never added; a condition block (aws:SourceArn or aws:SourceAccount) copied from another service's example that Lambda's assume call doesn't satisfy; and a Deny statement somewhere in the same document. In the console, this is IAM → Roles → your role → Trust relationships → Edit trust policy; paste the JSON above and save.
One thing the trust policy does not need: your own user or account as a principal. Lambda assumes the role; that is its job, not yours. The execution-role guidance says the same: calling sts:AssumeRole on the execution role from inside your function is unnecessary, and if you truly need the role to assume itself, the role has to trust itself explicitly.
Cause two: the role is correct and seconds old
Here is the part that makes people doubt their own eyes. IAM is a global service that replicates every change across Regions and to every other AWS service. The IAM troubleshooting guidance states it plainly: changes you make are not always immediately visible, because IAM uses eventual consistency, and it tells you to wait for changes to propagate before production workflows depend on them. In practice that window is seconds, usually under ten, occasionally longer. A script that creates a role and creates the function in the next line is racing that window, and sometimes loses.
The symptoms are unmistakable once you know them: the trust policy is perfect, the first attempt fails, the same command succeeds a moment later, and the problem comes and goes between runs. The fix is to retry with a pause. In a shell script:
for i in 1 2 3 4 5 6; do
aws lambda create-function --function-name booking-fn \
--runtime nodejs22.x --handler index.handler \
--role arn:aws:iam::123456789012:role/booking-fn-role \
--zip-file fileb://fn.zip && break
echo "attempt $i failed, waiting 10 s"; sleep 10
done
Six attempts ten seconds apart is a minute, which is more than propagation ever needs; if the sixth attempt still fails, you are back in cause one. The same idea in Python with boto3, catching only this error so a real mistake still surfaces:
import time, boto3
from botocore.exceptions import ClientError
lam = boto3.client("lambda")
for attempt in range(6):
try:
lam.create_function(FunctionName="booking-fn", Runtime="nodejs22.x",
Handler="index.handler",
Role="arn:aws:iam::123456789012:role/booking-fn-role",
Code={"ZipFile": open("fn.zip", "rb").read()})
break
except ClientError as e:
msg = e.response["Error"]["Message"]
if "cannot be assumed by Lambda" not in msg or attempt == 5:
raise
time.sleep(10)
The same race exists on update-function-configuration --role when you switch a function to a role you just created, and after update-assume-role-policy: the fix you made in the section above also takes a few seconds to be seen. If you fixed the trust policy and the very next command still fails, that is propagation, not a second mistake.
Jake: "Ten seconds. The whole Tuesday was ten seconds?"
Ethan: "Ten seconds and a script that didn't know how to wait. The second run always worked because the role had caught up by then."
The fix in CloudFormation, SAM, Terraform and CDK
Infrastructure tools hit the same race in a more confusing costume, because the tool creates the role and the function for you and decides the order. Each one has its own correct answer.
CloudFormation
CloudFormation already waits for the role to exist before it creates a function that references it with !GetAtt Role.Arn; the reference creates the dependency. What it doesn't wait for is the role's policies when those are separate resources (AWS::IAM::Policy, AWS::IAM::RolePolicy), and a function can be created against a role whose trust is fine but whose attachments are still being written, which some deployments surface as this error or as a function that fails its first invocation. The fix is an explicit DependsOn from the function to the policy resource:
Resources:
FnRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal: { Service: lambda.amazonaws.com }
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
FnLogPolicy:
Type: AWS::IAM::RolePolicy
Properties:
RoleName: !Ref FnRole
PolicyName: booking-extra
PolicyDocument: { Version: "2012-10-17", Statement: [ ... ] }
BookingFn:
Type: AWS::Lambda::Function
DependsOn: FnLogPolicy
Properties:
Role: !GetAtt FnRole.Arn
Runtime: nodejs22.x
Handler: index.handler
Code: { S3Bucket: my-bucket, S3Key: fn.zip }
Two template mistakes produce cause one rather than timing: a Principal written as a string (Principal: lambda.amazonaws.com) instead of a map with the Service key, and an AssumeRolePolicyDocument pasted from an EC2 role. If the template creates the role with a fixed RoleName, the stack also needs CAPABILITY_NAMED_IAM, which is a different error with its own page in the cards below.
SAM
A AWS::Serverless::Function with Policies: gets a role generated by SAM with the correct trust policy and the dependencies in the right order; this error rarely appears on that path. It appears when you pass your own role through the Role: property: then the function depends only on what you reference, so the same DependsOn as CloudFormation applies, and the trust policy of that role is yours to get right.
BookingFn:
Type: AWS::Serverless::Function
DependsOn: FnLogPolicy
Properties:
Role: !GetAtt FnRole.Arn
Runtime: nodejs22.x
Handler: index.handler
CodeUri: ./src
Terraform
Terraform's own example for aws_lambda_function shows the pattern: the role from a trust policy document, a policy attachment, and the function with an explicit depends_on on the attachment, so the function isn't created while the role's permissions are still landing. Without it, Terraform knows the function needs the role (it reads aws_iam_role.example.arn) but not that it should wait for the attachment:
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
}
}
resource "aws_iam_role" "lambda" {
name = "booking-fn-role"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
resource "aws_iam_role_policy_attachment" "lambda_logs" {
role = aws_iam_role.lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_lambda_function" "booking" {
function_name = "booking-fn"
role = aws_iam_role.lambda.arn
runtime = "nodejs22.x"
handler = "index.handler"
filename = "fn.zip"
depends_on = [aws_iam_role_policy_attachment.lambda_logs]
}
If the error still appears on a fresh apply with that in place, run terraform apply again before changing anything: the provider retries some IAM propagation errors on its own, but not every path, and a second apply after a few seconds is the honest test. A persistent failure means the trust document above is wrong, often because type was set to AWS instead of Service.
CDK
CDK writes the trust policy for you when you let it: new lambda.Function(...) without a role creates a role with lambda.amazonaws.com as the principal and orders the resources correctly. The error shows up when you build the role yourself with the wrong principal:
const role = new iam.Role(this, "BookingFnRole", {
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), // not "ec2.amazonaws.com"
managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")],
});
new lambda.Function(this, "BookingFn", {
runtime: lambda.Runtime.NODEJS_22_X,
handler: "index.handler",
code: lambda.Code.fromAsset("src"),
role,
});
Passing the role object (not its ARN as a string) is what lets CDK add the dependency; a role ARN imported with iam.Role.fromRoleArn from another stack gives CDK nothing to wait for, so make sure that stack deployed first.
Lambda@Edge: two principals, not one
A function you attach to a CloudFront distribution is replicated to edge locations by a different service, and its role must trust both the Lambda service and the Lambda@Edge replicator. A role that works for an ordinary function fails here with the same error until the trust policy lists both:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [ "lambda.amazonaws.com", "edgelambda.amazonaws.com" ]
},
"Action": "sts:AssumeRole"
}
]
}
Lambda@Edge also creates a service-linked role, AWSServiceRoleForLambdaReplicator, which trusts replicator.lambda.amazonaws.com; you don't edit that one, and it is not the role this error is about.
Not this error: the PassRole denial on the same command
The other role error that lands on CreateFunction reads User: arn:aws:iam::123456789012:user/ethan is not authorized to perform: iam:PassRole on resource: arn:aws:iam::123456789012:role/booking-fn-role. It looks like a cousin and it is the opposite problem: Lambda could assume the role fine, but you, the identity running the deploy, aren't allowed to hand that role to a service. That is fixed in the deployer's identity policy with an iam:PassRole allow on the role, not in the trust policy. Our PassRole guide covers it; the short version of the difference:
| Message | Who is being refused | Where the fix lives |
|---|---|---|
| The role defined for the function cannot be assumed by Lambda | The Lambda service, trying to assume the role | The role's trust policy (or wait for propagation) |
| ... is not authorized to perform: iam:PassRole on resource ... | You, trying to pass the role to Lambda | Your identity policy: allow iam:PassRole on that role ARN |
| ... is not authorized to perform: lambda:CreateFunction | You, trying to create the function | Your identity policy: allow lambda:CreateFunction |
| AccessDenied on sts:AssumeRole (from your terminal) | A person or CI job assuming a role | The trust policy must name that person's account or role, a different page |
The worked example: Ethan's deploy script
Ethan's script did four things in order: create the role, attach AWSLambdaBasicExecutionRole, zip the code, create the function. The trust policy was right; the diagnosis command printed lambda.amazonaws.com. The failure was the fourth line running about a second after the first. He made two changes. The function creation went inside the six-attempt loop with a ten-second sleep, and the script now checks aws sts get-caller-identity first and refuses to run if the account doesn't match the one in the role ARN, because the second time this error appeared, on Thursday, it was a copy of the script run with Jake's personal profile against Jake's own account, where no such role existed at all. Different cause, same message, same one-command diagnosis.
Jake: "So on Thursday the role really didn't exist, and on Tuesday it existed but wasn't ready?"
Ethan: "Right. And both times the fix started with reading the role back instead of staring at the error."
The checklist, in order
- Read the trust policy of the exact role in the ARN:
aws iam get-role --role-name ROLE --query 'Role.AssumeRolePolicyDocument'. - If
lambda.amazonaws.comis missing, replace the trust policy with the JSON above; addedgelambda.amazonaws.comfor Lambda@Edge. - If the trust policy is right and the role or its trust policy changed in the last minute, wait 10 seconds and retry, up to six times.
- Check the account: the role ARN's account must match
aws sts get-caller-identity, and the ARN must be a role, not a user or an instance profile. - In CloudFormation and SAM, add
DependsOnfrom the function to the role's policy resources; in Terraform,depends_onon the policy attachment; in CDK, pass the role object. - If the message says
iam:PassRole, stop: that's your permission, not the trust policy.
Frequently asked questions about "The role defined for the function cannot be assumed by Lambda"
What does "The role defined for the function cannot be assumed by Lambda" mean?
Lambda tried to assume the execution role you gave it and was refused: either the role's trust policy does not name lambda.amazonaws.com, or the role was created or changed seconds ago and IAM has not finished propagating it.
What should a Lambda execution role trust policy look like?
An Allow statement with Principal set to {"Service": "lambda.amazonaws.com"} and Action sts:AssumeRole. For Lambda@Edge, add edgelambda.amazonaws.com to the Service list.
How do I check a role's trust policy from the CLI?
aws iam get-role --role-name ROLE --query 'Role.AssumeRolePolicyDocument'. The Principal must include the Lambda service.
How do I fix the trust policy on an existing role?
aws iam update-assume-role-policy --role-name ROLE --policy-document file://trust.json, or edit Trust relationships on the role in the IAM console. Existing functions keep working.
Why does the same create-function command work on the second try?
IAM is eventually consistent. A role created a second earlier can be invisible to Lambda for a few seconds; the retry lands after propagation. Wait about 10 seconds and retry, up to six times.
How long does IAM role propagation take?
Usually a few seconds, occasionally longer. A six-attempt loop with 10-second waits covers it; if that fails, the trust policy is wrong.
How do I fix this error in CloudFormation?
Reference the role with !GetAtt Role.Arn, write the Principal as a map with the Service key, and add DependsOn from the function to any separate IAM policy resources on that role.
How do I fix this error in Terraform?
Use a principals block of type Service with lambda.amazonaws.com in the assume-role policy, and add depends_on = [aws_iam_role_policy_attachment.NAME] to the aws_lambda_function.
How do I fix this error in CDK?
Create the role with assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com") and pass the role object to the Function, so CDK orders the resources; or omit the role and let CDK create it.
Does SAM need a trust policy for its functions?
Not when you use Policies: SAM generates the role with the right trust. If you pass your own Role, that role's trust policy and a DependsOn on its policies are your responsibility.
Is this the same as the iam:PassRole AccessDenied error?
No. PassRole is refused to you, the deployer, and is fixed in your identity policy. "Cannot be assumed by Lambda" is refused to the Lambda service and is fixed in the role's trust policy.
Can a role from another AWS account be used as a Lambda execution role?
No. The execution role must be in the same account as the function. A role ARN with another account ID produces this error.
Can I use an instance profile ARN as the Lambda role?
No. An instance profile is an EC2 wrapper around a role; pass the role ARN itself, arn:aws:iam::ACCOUNT:role/NAME.
Why does Lambda@Edge still fail with a working Lambda role?
Lambda@Edge replicates the function through a second service, so the trust policy must list both lambda.amazonaws.com and edgelambda.amazonaws.com.
Does update-function-configuration --role hit the same error?
Yes. Switching a function to a role whose trust policy is wrong, or that was created seconds earlier, produces the same message; the same checks apply.
Should my function call sts:AssumeRole on its own execution role?
No. Lambda assumes the execution role for you. If a role must assume itself for a special case, the role has to trust itself explicitly in its trust policy.
Ethan's script has a ten-second patience built in now, and a line that refuses to run against the wrong account. The booking function deploys on the first try every time, which Jake finds less impressive than the Tuesday it didn't. Jake's verdict: "So the role was fine all along; it was just late." Ethan said that was the most accurate bug report the shop had ever filed.
📌 If you keep one line from this page
Read the role's trust policy with get-role: if lambda.amazonaws.com is missing, fix the trust policy; if it's there, the role is just seconds old, so wait 10 seconds and retry, and in CloudFormation, Terraform or SAM make the function depend on the role's policies.
Principal: {"Service": "lambda.amazonaws.com"} · Lambda@Edge adds edgelambda.amazonaws.com · PassRole is a different error · same account, role ARN not instance profile.
Revision note. Written October 15, 2026, with the Node.js 22 runtime in the examples and the current Terraform and CDK role patterns. "Patience is bitter, but its fruit is sweet," says the old proverb; here the fruit arrives in about ten seconds.