What it means
ResourceNotFoundException is one of the most-encountered errors in AWS and one of the least informative, because the thing it most often means is the thing it cannot say. The service checked for the resource you named, in the account it was called from and the region it was called in, and did not find it. It has no way of knowing the resource exists perfectly well somewhere else.
Region is the usual answer. An SDK client constructed without an explicit region takes it from AWS_REGION, which the execution environment sets to wherever the function is deployed. That is almost always what you want, and it means the same code targets us-east-1 in one deployment and eu-west-1 in another without anything in the source suggesting a region is involved. Code developed against a hardcoded region, or a client copied from a script that set one, changes behaviour on deploy in a way nothing in the diff shows.
The second recurring cause is a name that is empty. An environment variable that was never set resolves to undefined, gets passed as the table or queue name, and produces a not-found for a resource with no name at all. Read closely, the message often shows this — there is simply nothing between the colon and the words "not found" — but it reads as a formatting quirk rather than as the answer.
The subtlety worth carrying is that not-found and access-denied are not always distinct. Several AWS services deliberately return ResourceNotFoundException when the caller has no permission to know the resource exists, because confirming existence to an unauthorised caller is itself a small information leak. So a resource you can see in the console, in the right account and the right region, that the function insists is not there, may be a permissions problem wearing a not-found message. Checking the execution role before assuming a deployment problem is worth the minute it takes.
CloudWatch’s Errors metric: Whether CloudWatch’s Errors metric counts this depends on whether the error escaped your handler, which the log line alone does not settle. Counted only when the exception escapes the handler. This is deterministic rather than transient, so affected requests fail consistently. LogStitch reports the example below as caught.
What it looks like in CloudWatch
This is the shape the failure arrives in: the lines of one invocation scattered among everything else the log group received at the same moment.
CloudWatch Logs Before
7 raw lines, in the order CloudWatch delivered them.
LogStitch After
The same lines, grouped into the invocation they belong to.
Error3b8d5f02-6a17-4e40-9c25-8f1a7b3e0d62
- 16:31:44.114PLAT
START RequestId: 3b8d5f02-6a17-4e40-9c25-8f1a7b3e0d62 Version: $LATEST
- 16:31:44.118INFO
INFO Looking up order ord_92ba7c
- 16:31:44.244ERROR
ERROR ResourceNotFoundException: Requested resource not found: Table: orders not found at throwDefaultError (/var/task/node_modules/@smithy/smithy-client/dist-cjs/index.js:867:20) at loadOrder (/var/task/src/orders.js:24:9)3 lines - 16:31:44.402PLAT
END RequestId: 3b8d5f02-6a17-4e40-9c25-8f1a7b3e0d62
- 16:31:44.402PLAT
REPORT RequestId: 3b8d5f02-6a17-4e40-9c25-8f1a7b3e0d62 Duration: 288.44 ms Billed Duration: 289 ms Memory Size: 512 MB Max Memory Used: 94 MB Status: error
The panel on the right is generated by running the excerpt on the left through the same parser that powers the free web stitcher — it is what the tool actually produces for this input, not an illustration of it.
How to confirm it from the logs
Read the resource identifier in the message and check its region and account. If it is an ARN, both are in it and the comparison is direct. If it is a bare name — a table, a queue, a secret — the region is implicit in the client's configuration, which means the message cannot show you the thing most likely to be wrong.
Distinguish it from a permissions failure, because the two are easy to confuse and IAM sometimes chooses this deliberately. AccessDeniedException says you may not; ResourceNotFoundException says there is nothing there. But some services return not-found rather than denied when the caller has no permission to know the resource exists, so a resource you can see in the console and the function cannot find is worth checking as a permissions problem too.
Causes, most likely first
The function and the resource are in different regions
Compare the region in the resource ARN, or the client's configured region, against the function's region. An SDK client with no explicit region uses AWS_REGION from the execution environment, so code that worked against a hardcoded region locally silently targets a different one in production.
The resource name comes from an environment variable that is unset
Check whether the name in the message is empty, literally undefined, or a placeholder. An unset variable produces a lookup for a resource that was never going to exist, and the message shows the empty name if you read it closely.
The resource was renamed, deleted, or never deployed to this environment
List the resource in the target account and region. A stack deployed to staging but not production, or a table renamed in a migration, produces a genuine not-found while the configuration looks entirely reasonable.
The call is cross-account and the resource is elsewhere
Check the account number in the ARN against the function's account. Cross-account access needs both a resource policy allowing your role and the correct ARN; getting the ARN wrong produces not-found rather than denied.
IAM is returning not-found instead of denied
Confirm the resource exists in that account and region, then check the execution role. Some services deliberately return ResourceNotFoundException when the caller lacks permission to know the resource exists, so a resource you can see and the function cannot is a permissions question.
Fixes
Log the resolved identifier alongside the failure
The fastest fix is making the error self-explanatory. Logging the region, the account and the name the function actually used turns "not found" into "not found — because we looked in us-west-2".
jsconsole.log("config", {
region: process.env.AWS_REGION,
table: process.env.TABLE_NAME,
function: process.env.AWS_LAMBDA_FUNCTION_NAME
});
Fail at startup when required configuration is missing
An unset environment variable should stop the function with a message naming the variable, not produce a lookup for an empty resource name several frames later. Checking at module scope makes it a clear init failure instead.
jsfunction required(name) {
const value = process.env[name];
if (!value) throw new Error(`${name} is not set`);
return value;
}
// Module scope: fails once, loudly, rather than per request and obscurely.
const TABLE_NAME = required("TABLE_NAME");
Pass resource identifiers from the stack rather than typing them
Referencing the resource in the template means the name, region and account are always right and always move together with the deployment.
yamlResources:
OrdersFunction:
Type: AWS::Serverless::Function
Properties:
Environment:
Variables:
TABLE_NAME: !Ref OrdersTable
QUEUE_URL: !Ref OrderQueue
Set the region explicitly when the target is not the function's own
An SDK client defaults to the function's region. When the resource genuinely lives elsewhere, say so at the client rather than relying on an ambient default.
jsconst client = new DynamoDBClient({ region: process.env.TABLE_REGION ?? process.env.AWS_REGION });
Also seen as
The same underlying failure, worded differently by a different runtime, SDK version, or logging layer. All of these land here — there is no separate page for each phrasing.
Related errors
Errors that show up alongside this one, or that people mistake for it.
References
- AWS Lambda Developer Guide — Lambda runtime environment variables
- Amazon DynamoDB Developer Guide — Error handling
LogStitch finds this automatically, across every invocation in your account.
Paste a log excerpt into the free web stitcher and see it grouped, classified, and measured in your browser — nothing is uploaded. Or run the Mac app against your own AWS profiles and get the same view over every function you own.