Bug: `sam build --cached` doesn't `NpmInstall` when `package-lock.json` changes
Description:
I have Node Lambda functions deployed with SAM and they each have various dependencies. This project is wired up with continuous deployment on GitHub. GitHub's Dependabot will often create security patch updates that only modify package-lock.json, where the dependency has a version range, such as ^3.0.0.
Because nothing else asides from package-lock.json has changed inside the Lambda function's source directory, sam build --cached fails to call NpmInstall, leaving the function with its existing, vulnerable dependencies.
I'm aware this specific issue can be worked around by using https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#versioning-strategy-- to change package.json (which forces SAM to rebuild), but other processes also only trigger changes to package-lock.json - another that affects me is such as use of Local Paths in package.json, which don't have a version to modify within package.json, but does affect the contents of package-lock.json when the local module's deps change.
Steps to reproduce:
- Run
sam initand create a Node JS project. - Run
sam build --cachedand observeNodejsNpmBuilder:NpmInstallis run. - Make any change to
hello-world/package-lock.json, such as a dependency change. - Re-run
sam build --cachedand observeNodejsNpmBuilder:NpmInstallis not run. - Observe that
.aws-sam/build/HelloWorldFunction/node_modules/contents remain unchanged.
Observed result:
$ sam build --cached
Starting Build use cache
Manifest file is changed (new hash: c0e83ff3ce1bd02a0c2a7c02974c24ec) or dependency folder (.aws-sam/deps/668cf3e2-6cd7-4f49-a152-4681eab3f16a) is missing for
(HelloWorldFunction), downloading dependencies and copying/building source
Building codeuri: /home/user/sam-app/hello-world runtime: nodejs22.x architecture: x86_64 functions: HelloWorldFunction
Running NodejsNpmBuilder:NpmPack
Running NodejsNpmBuilder:CopyNpmrcAndLockfile
Running NodejsNpmBuilder:CopySource
Running NodejsNpmBuilder:NpmInstall
Running NodejsNpmBuilder:NpmTest
Running NodejsNpmBuilder:CleanUp
Running NodejsNpmBuilder:CopyDependencies
Running NodejsNpmBuilder:CleanUpNpmrc
Running NodejsNpmBuilder:LockfileCleanUp
Running NodejsNpmBuilder:LockfileCleanUp
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided
$ echo 'uhoh' > hello-world/package-lock.json # Or actually update a dependency that should be reinstalled and updated...
$ sam build --cached
Starting Build use cache
Manifest is not changed for (HelloWorldFunction), running incremental build
Building codeuri: /home/user/sam-app/hello-world runtime: nodejs22.x architecture: x86_64 functions: HelloWorldFunction
Running NodejsNpmBuilder:NpmPack
Running NodejsNpmBuilder:CopyNpmrcAndLockfile
Running NodejsNpmBuilder:CopySource
Running NodejsNpmBuilder:CopySource
Running NodejsNpmBuilder:CleanUpNpmrc
Running NodejsNpmBuilder:LockfileCleanUp
Running NodejsNpmBuilder:LockfileCleanUp
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided
Expected result:
SAM to consider contents of package-lock.json as part of the "manifest" for Node projects, since it dictates the precise versions and checksums of what gets installed.
Additional environment details (Ex: Windows, Mac, Amazon Linux etc)
- OS: Ubuntu 22.04
sam --version:SAM CLI, version 1.143.0- AWS region: us-east-1
@davidjb Thanks for the detailed bug report with clear reproduction steps! This is definitely a legitimate issue with how sam build --cached determines whether to rebuild a function.
The root cause is that SAM's caching mechanism is currently only checking for changes in source files and package.json, but not package-lock.json. When Dependabot updates only the lockfile (which is the correct behavior for transitive dependency updates within semver ranges), SAM's cache invalidation logic doesn't detect this as a meaningful change, so it skips the NpmInstall step entirely. This leaves your deployed functions with the old, potentially vulnerable dependency versions even though the lockfile explicitly specifies newer ones.
The fix should be in the cache invalidation logic where SAM determines the hash/fingerprint of the build artifacts. The code likely lives somewhere in the build workflow cache checking, and it needs to include package-lock.json (and similar lockfiles like yarn.lock, pnpm-lock.yaml) as part of the cache key calculation. These lockfiles are the source of truth for what actually gets installed, so they absolutely should trigger cache invalidation when modified.
Your workaround of using versioning-strategy: increase is reasonable for the Dependabot case, but as you noted, it doesn't help with local path dependencies or other scenarios where only the lockfile changes. For now, you could also work around this by either skipping --cached when you know lockfiles have changed, or by touching a source file to force invalidation, but neither is ideal. This should definitely be fixed in SAM itself to properly track lockfile changes as part of the build cache key.