The link that @IsmailBadawi provided in comments is the key here.
Make determines if a target is out of date based on its last-modified time (mtime). However, a directory is only considered to be modified when one of its direct children are added/removed/renamed (at least on Unix-like systems).
This is insufficient in your case. Imagine the following sequence:
Run make
for the first time.
- Rule is not skipped.
mtime(node_modules) > mtime(package.json)
.
Run make
again.
Change a dependency version in package.json
.
mtime(node_modules) < mtime(package.json)
.
Run make
again.
- Rule is not skipped.
- Dependency is updated in
node_modules
.
- But
mtime(node_modules)
is unaffected.
Run make
again.
... and so on forever ...
You can probably fix this by adding touch -m node_modules
to your recipe.