Error: ENOSPC: no space left on device, write

Short answer

A write to /tmp failed because the function's ephemeral storage is full. The trap is that /tmp is not cleared between invocations — a warm container keeps every file previous invocations left behind, so this fails after hundreds of successful runs rather than on the first one.

What it means

Every Lambda execution environment gets a writable /tmp directory, sized by the function's ephemeral storage setting — 512 MB by default, configurable up to 10 GB. It is real disk, it is fast, and it is the only writable location in the filesystem. ENOSPC means a write to it failed because it was full.

The detail that makes this error behave strangely is lifetime. /tmp belongs to the execution environment, not to the invocation. Lambda reuses environments to avoid cold starts, and everything in /tmp persists across that reuse. A function that writes a 20 MB file and does not delete it will succeed twenty-five times and fail on the twenty-sixth — on that container. A different container, freshly started, handles the same request perfectly.

That produces a failure pattern that is genuinely confusing when observed one invocation at a time: an error rate that is neither zero nor total, that does not correlate with input, and that appears to move around. Each container independently accumulates until it hits the wall, then keeps failing every request routed to it until it is recycled. Seen per-invocation it looks random; seen per-execution-environment it is a clean sawtooth — a run of successes, a cliff, and then a fresh container starting the climb again.

The same persistence is also useful, and worth keeping rather than fighting. Caching a font set, a model file, or a downloaded certificate in /tmp on the first invocation and reusing it on subsequent ones is a legitimate and effective optimisation — that is exactly what warm containers are for. The distinction is between a bounded cache, which reaches a stable size, and unbounded accumulation, which does not. Files named after the request ID are the classic version of the second: every invocation adds one, and nothing ever removes it.

Counts

CloudWatch’s Errors metric: The error escaped your handler, so Lambda reports the invocation as failed and CloudWatch’s Errors metric counts it. An uncaught ENOSPC fails the invocation and CloudWatch counts it; code that catches it and returns normally is not counted. LogStitch classified the example below from its log level rather than an extracted error type, so it reports the failure as caught — read the stack in your own logs to see whether anything actually handled it.

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

9 raw lines, in the order CloudWatch delivered them.

2026-08-29T13:07:41.220Z START RequestId: 2e6f1a83-4d70-4c25-9b3e-7a1c8d40f562 Version: $LATEST
2026-08-29T13:07:41.224Z 2e6f1a83-4d70-4c25-9b3e-7a1c8d40f562 INFO Render job accepted doc=inv_88214 pages=42
2026-08-29T13:07:41.902Z 2e6f1a83-4d70-4c25-9b3e-7a1c8d40f562 INFO Fonts cached to /tmp/fonts
2026-08-29T13:07:43.118Z 2e6f1a83-4d70-4c25-9b3e-7a1c8d40f562 ERROR Error: ENOSPC: no space left on device, write
at Object.writeSync (node:fs:936:3)
at writeChunk (/var/task/node_modules/pdf-lib/dist/writer.js:118:14)
at renderDocument (/var/task/src/render.js:63:9)
2026-08-29T13:07:43.402Z END RequestId: 2e6f1a83-4d70-4c25-9b3e-7a1c8d40f562
2026-08-29T13:07:43.402Z REPORT RequestId: 2e6f1a83-4d70-4c25-9b3e-7a1c8d40f562 Duration: 2182.44 ms Billed Duration: 2183 ms Memory Size: 1024 MB Max Memory Used: 288 MB Status: error

LogStitch After

The same lines, grouped into the invocation they belong to.

Error2e6f1a83-4d70-4c25-9b3e-7a1c8d40f562dur 2.18sbilled 2.18smem 288/1024MBlogs 6
  1. 13:07:41.220PLAT
    START RequestId: 2e6f1a83-4d70-4c25-9b3e-7a1c8d40f562 Version: $LATEST
  2. 13:07:41.224INFO
    INFO	Render job accepted doc=inv_88214 pages=42
  3. 13:07:41.902INFO
    INFO	Fonts cached to /tmp/fonts
  4. 13:07:43.118ERROR
    ERROR	Error: ENOSPC: no space left on device, write
        at Object.writeSync (node:fs:936:3)
        at writeChunk (/var/task/node_modules/pdf-lib/dist/writer.js:118:14)
        at renderDocument (/var/task/src/render.js:63:9)
    4 lines
  5. 13:07:43.402PLAT
    END RequestId: 2e6f1a83-4d70-4c25-9b3e-7a1c8d40f562
  6. 13:07:43.402PLAT
    REPORT RequestId: 2e6f1a83-4d70-4c25-9b3e-7a1c8d40f562	Duration: 2182.44 ms	Billed Duration: 2183 ms	Memory Size: 1024 MB	Max Memory Used: 288 MB	Status: error
Error
Status
2.18s
Duration
2.18s
Billed
288/1024MB
Memory
72%
Headroom
No
Cold start
LogLevelError
Error type

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

Look at when in the container's life it happens. ENOSPC on a cold start means one invocation genuinely wrote more than the ephemeral storage allows. ENOSPC after many successful invocations on the same execution environment means accumulation — each run leaving a file behind until the sum crosses the limit.

The pattern to look for across a log stream is a run of successes followed by a cliff, then another run of successes on a fresh container. That sawtooth is the signature, and it is invisible if you look at the failing invocation alone: nothing in it explains why the same code worked a minute earlier.

Causes, most likely first

1

Temporary files are written and never deleted

How to confirm

Check whether the code removes what it writes, on every path including error paths. A download, an intermediate render, or an extracted archive left in /tmp survives the invocation and is still there for the next one on that container.

2

A single invocation writes more than the configured ephemeral storage

How to confirm

Compare the size being written against the function's EphemeralStorage setting, which defaults to 512 MB and can be raised to 10,240 MB. If the failure happens on a cold start too, this is a per-invocation sizing problem rather than an accumulation one.

3

A library uses /tmp without telling you

How to confirm

Look for dependencies that spool to disk — file-upload parsers, image and PDF tools, compression libraries, and some database drivers all write temporary files. If your own code never touches /tmp and it still fills, something underneath you is using it.

4

Files are written under unique names each time

How to confirm

Check whether the filename includes a request ID, a timestamp, or a UUID. Writing to a fixed path overwrites and stays flat; writing to a unique path accumulates one file per invocation, which is the fastest way to fill the space.

Fixes

Fix 1

Delete what you write, in a finally block

Cleanup has to run on the failure paths too, or an error partway through leaves the file behind — and errors are exactly when this is most likely. A finally is the only placement that survives every exit route.

jsimport { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";

export const handler = async (event) => {
  const dir = await mkdtemp(join(tmpdir(), "job-"));
  try {
    await download(event.key, join(dir, "input.bin"));
    return await transform(dir);
  } finally {
    // Runs on success and on failure — the failure path is the one that matters.
    await rm(dir, { recursive: true, force: true });
  }
};
Fix 2

Raise the ephemeral storage when one invocation genuinely needs it

/tmp defaults to 512 MB and can be configured up to 10 GB. This is the right fix when a single invocation legitimately handles files that large, and the wrong one when the space is being consumed by leftovers — that only delays the failure.

yamlResources:
  TranscodeFunction:
    Type: AWS::Serverless::Function
    Properties:
      EphemeralStorage:
        Size: 2048   # MB; 512 default, 10240 maximum
Fix 3

Stream through memory instead of staging on disk

Where a file is only ever read once on its way somewhere else, /tmp is an unnecessary stop. Piping the source straight to the destination removes the disk usage entirely and is usually faster.

jsimport { pipeline } from "node:stream/promises";

// No intermediate file, so nothing to accumulate or clean up.
await pipeline(
  (await s3.send(new GetObjectCommand(source))).Body,
  createGunzip(),
  uploadStream(destination)
);
Fix 4

Check what is already there when you suspect accumulation

Logging the contents of /tmp at the start of an invocation confirms accumulation in one deployment, and shows you which files are being left behind.

jsimport { readdir, stat } from "node:fs/promises";

const names = await readdir("/tmp");
const sizes = await Promise.all(
  names.map(async (name) => (await stat(`/tmp/${name}`)).size)
);
console.log("tmp", { files: names.length, bytes: sizes.reduce((a, b) => a + b, 0) });

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.

OSError: [Errno 28] No space left on deviceENOSPC: no space left on device, open '/tmp/upload.dat'lambda no space left on devicelambda tmp fulllambda ephemeral storage exceeded

Errors that show up alongside this one, or that people mistake for it.

References

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.