aws-sam-cli icon indicating copy to clipboard operation
aws-sam-cli copied to clipboard

Bug: `sam build --cached` doesn't `NpmInstall` when `package-lock.json` changes

Open davidjb opened this issue 3 months ago • 1 comments

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:

  1. Run sam init and create a Node JS project.
  2. Run sam build --cached and observe NodejsNpmBuilder:NpmInstall is run.
  3. Make any change to hello-world/package-lock.json, such as a dependency change.
  4. Re-run sam build --cached and observe NodejsNpmBuilder:NpmInstall is not run.
  5. 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)

  1. OS: Ubuntu 22.04
  2. sam --version: SAM CLI, version 1.143.0
  3. AWS region: us-east-1

davidjb avatar Sep 03 '25 05:09 davidjb

@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.

dcabib avatar Oct 03 '25 22:10 dcabib