What it means
A Lambda invocation on a cold start is two phases sharing one clock. Init loads the runtime, imports your module, and runs everything at module scope. Invoke calls your handler. The configured timeout covers both, and when initialization is slow enough it can consume the whole budget before the handler is ever called.
That produces a timeout whose logs look wrong. The usual timeout shows a handler making progress and then stopping mid-work. This one shows imports completing, a client being constructed, a configuration being loaded — and then nothing, because the thing you expected to see next never started. AWS names this case Sandbox.Timedout to distinguish it from a handler that simply ran long.
The reason it hides so well is that it only happens on cold starts. Warm invocations skip initialization entirely and finish in milliseconds, so the function looks fast in every dashboard and every load test — load tests in particular keep environments warm by construction. The failures arrive scattered, during quiet periods when environments are being created rather than reused, and at deployment time when every environment is new at once.
There is a trap in how the timeout gets chosen. A handler that reliably runs in 200 ms invites a short timeout, and a short timeout is good practice — it fails fast and costs less. But if the same function takes 1.8 seconds to import its dependencies, a two-second timeout leaves 200 milliseconds for the handler on any cold start, and the margin that looked generous is nonexistent. Reading Init Duration from a successful invocation's REPORT line is what makes that visible, and it is the number to size the timeout against.
Two of the fixes are worth trying before raising the timeout, because they make the function genuinely better rather than merely more patient. Lazy imports move cost off the initialization path for requests that do not need it. And because Lambda scales CPU with memory, and initialization is mostly CPU-bound work — parsing, linking, loading — raising memory frequently shortens Init Duration enough to resolve this on its own, sometimes without increasing the bill at all.
CloudWatch’s Errors metric: The error escaped your handler, so Lambda reports the invocation as failed and CloudWatch’s Errors metric counts it. LogStitch classified the invocation above as uncaught.
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
6 raw lines, in the order CloudWatch delivered them.
LogStitch After
The same lines, grouped into the invocation they belong to.
Timeout6c1b8e40-3f27-4a95-b012-7d4e9a2c5b81
- 10:00:00.104INFO
[INFO] Importing pandas and pyarrow
- 10:00:01.902INFO
[INFO] Loading model weights from /opt/model
- 10:00:03.100
6c1b8e40-3f27-4a95-b012-7d4e9a2c5b81 Task timed out after 3.00 seconds
- 10:00:03.102PLAT
REPORT RequestId: 6c1b8e40-3f27-4a95-b012-7d4e9a2c5b81 Duration: 3000.00 ms Billed Duration: 3000 ms Memory Size: 1024 MB Max Memory Used: 402 MB Status: timeout
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
The tell is what is missing. A normal timeout shows your handler logging progress and then stopping; this one shows initialization output — imports, client construction, config loading — and then nothing, because the handler never started. If the last line before the timeout is something your module does at load time, this is the shape.
Compare cold and warm invocations of the same function. If the failures land only on cold starts and warm ones are comfortably fast, the work is in initialization. AWS documents this case as Sandbox.Timedout, distinguishing it from a handler that simply ran long.
Check Init Duration on the REPORT line of the invocations that do succeed. A figure close to your configured timeout means the margin is already thin and the failures are a matter of variance rather than of anything changing.
Causes, most likely first
Heavy imports at module scope
Look at the last thing logged before the timeout, and at what your module imports. Large libraries — a data-science stack, an ORM, a machine-learning framework — can take seconds to import, and that happens once per execution environment, entirely inside the timeout.
Network calls during initialization
Check for configuration fetches, secret retrievals or connection warm-ups outside the handler. These are usually deliberate — they exist to make invocations faster — and they move the cost to a place where it is easy to forget the timeout still applies.
A short timeout that suits the handler but not the cold start
Compare the configured timeout against Init Duration plus a typical Duration. A function whose handler reliably runs in 200 ms looks like it needs a two-second timeout — until a 1.8-second initialization is added to it on every cold start.
Initialization is retried, consuming the budget twice
Look for repeated initialization output within one invocation. When init fails, Lambda can retry it inside the same invocation, so a first attempt that hangs leaves considerably less time for the second.
Fixes
Raise the timeout to cover initialization, not just the handler
The budget has to accommodate the worst case, which is a cold start. Take a real Init Duration from a REPORT line, add typical handler time, and leave margin — a timeout tuned only to warm invocations will fail whenever a new execution environment is created.
yamlResources:
ReportFunction:
Type: AWS::Serverless::Function
Properties:
# Observed Init Duration ~1.9s, handler ~0.3s.
Timeout: 10
MemorySize: 1024 # more memory means more CPU, so init is faster too
Import lazily so cold starts only pay for what the request needs
Moving a heavy import inside the branch that uses it takes it off the initialization path entirely. Requests that need it pay once; requests that do not, never do.
python# Module scope: every cold start pays for this, used or not.
# import pandas as pd
def lambda_handler(event, context):
if event.get("format") != "parquet":
return handle_simple(event)
# Only this branch pays the import cost.
import pandas as pd
return handle_parquet(event, pd)
Give initialization more CPU by giving it more memory
Lambda scales CPU with memory, and initialization is usually CPU-bound — parsing, linking and loading rather than waiting. Raising memory often shortens Init Duration enough to fix this without touching any code, and can leave the bill flat because the function finishes sooner.
Use provisioned concurrency when the initialization is irreducible
Where a function genuinely needs seconds to start, provisioned concurrency moves that work off the request path — environments are initialised in advance rather than while someone waits. initializationType on the platform.initStart record tells you whether it is being used.
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 — Troubleshoot invocation issues
- AWS Lambda Developer Guide — Lambda execution environment lifecycle
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.