What it means
Lambda's validation of a deployment package is shallow and early: it takes the bytes, tries to open them as a zip, and refuses if it cannot. InvalidZipFileException means that failed. Nothing has been inspected — no handler resolved, no dependency checked — because the archive never opened.
That makes this one of the easier errors in the index to localise, because Lambda is almost certainly not the problem. The archive it received is not a readable zip, and the archive is produced entirely by your build. The interesting question is which of a small number of ways that happened.
The most common, and the most demoralising, is an empty package. A build step fails, the pipeline does not stop because nothing checked its exit status, a subsequent zip runs against a directory that is not there, and a tiny archive uploads without complaint. Everything about the run looks successful until Lambda declines to open the result. A size assertion in the packaging step costs one line and catches it every time.
The second is encoding. The AWS CLI distinguishes file://, which reads the argument as text, from fileb://, which reads it as binary. Using the first on a zip mangles it in a way that produces exactly this error and no hint about the cause. The same class of mistake appears in scripts that base64-encode a payload the SDK was going to encode anyway.
Neither of these leaves anything in CloudWatch Logs. The function is not created or updated, so it writes nothing — the entire failure lives in the response to the API call, which is why the verification worth having is file and unzip -t in the build, before the upload ever happens.
Where you'll see it
Deployment output
Not in CloudWatchThis failure happens before the function runs, so nothing about it reaches CloudWatch Logs — there is no invocation, and no log group entry to find. Once the deployment succeeds and the function starts running, the rest of this index covers what you will see there.
Causes, most likely first
The archive is empty or was never written
Check the file's size before it is uploaded. A build step that failed but did not stop the pipeline, or a zip invocation whose input glob matched nothing, produces a zero-byte or near-empty file that uploads perfectly and cannot be opened.
It is a tarball or gzip, not a zip
Run file on the artifact. Lambda's zip package type accepts only zip archives, and .tar.gz is easy to produce by accident in a pipeline that also builds container images or that reuses a packaging script from another target.
The upload was truncated
Compare the size of the local file against what was uploaded, and check for a retried or interrupted transfer. A partial upload to S3 followed by a deploy referencing that key produces a file that is a valid prefix of a zip and not a valid zip.
Base64 encoding was applied twice, or not decoded
Check how the artifact reaches the API. Some SDKs and CLI paths expect raw bytes and others expect base64; encoding an already-encoded payload, or passing base64 where bytes were wanted, both deliver something that is not an archive. Using fileb:// rather than file:// with the CLI is the usual fix.
The zip was built with a format Lambda does not read
Check which tool produced it. Archives using unusual compression methods, or encrypted zips, are valid zip files that Lambda will not open. Standard deflate is what to target.
Fixes
Verify the artifact before uploading it
Three commands settle this completely, and running them in CI catches the problem where it is cheap rather than at deploy time.
bash# Is it actually a zip, and is it a plausible size?
file function.zip
ls -lh function.zip
# Can it be opened and does it contain the handler?
unzip -t function.zip > /dev/null && echo "archive is valid"
unzip -l function.zip | grep -E 'index\.(js|mjs)|lambda_function\.py'
Fail the build when the package is empty
The empty-artifact case is worth guarding explicitly, because it is the one that reaches Lambda looking like a successful build. A size check is a one-line addition.
bashset -euo pipefail
zip -qr function.zip dist/ node_modules/ package.json
MIN_BYTES=10240
SIZE=$(wc -c < function.zip)
if [ "$SIZE" -lt "$MIN_BYTES" ]; then
echo "function.zip is only ${SIZE} bytes — the build produced nothing" >&2
exit 1
fi
Use fileb:// so the CLI sends raw bytes
file:// reads the argument as text and fileb:// reads it as binary. Using the first for a zip corrupts it in transit, and the resulting error says nothing about which of the two you used.
bash# Wrong: reads the archive as text.
# aws lambda update-function-code --zip-file file://function.zip
aws lambda update-function-code \
--function-name orders-fn \
--zip-file fileb://function.zip
Go through S3 for anything large
Direct upload is capped at 50 MB and is the path where truncation happens. Uploading to S3 and deploying from the key is more reliable, and S3 verifies the object on write.
bashaws s3 cp function.zip s3://my-artifacts/orders-fn/build-42.zip
aws lambda update-function-code \
--function-name orders-fn \
--s3-bucket my-artifacts --s3-key orders-fn/build-42.zip
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 deployment issues
- AWS Lambda Developer Guide — Deploy Lambda functions with .zip file archives
This one happens before there are any logs.
LogStitch reads CloudWatch, and a deployment that fails never writes to it — so this is not an error it can find for you. Once the function deploys and starts running, the free web stitcher groups its invocations in your browser, and the Mac app does the same across every function in your account.