JS-2022 - Fix npm cache never being saved due to lookup-only mode#7494
Conversation
When a branch changes package-lock.json (e.g. a new-rule PR adding a devDependency, or a Renovate bump), the npm cache is keyed on the lockfile hash. The gh-action_cache save step skips uploading a cache entry under that hash whenever the freshly installed content matches what's already cached for the default branch, relying on cross-branch fallback restores to cover it later. But the fallback lookup reuses the same (branch-specific) hash suffix instead of the default branch's own hash, so it misses whenever the lockfile actually differs from master - leaving jobs like prepare_rspec_rule_data with no node_modules at all and failing with "tsx: not found". Add a conditional npm ci fallback (mirroring the existing populate_npm_cache pattern) to every job that only restores the npm cache, so a miss no longer breaks the build.
Ruling ReportCode no longer flagged (2 issues)S8784vitest/test/typescript/failing/fail.test-d.ts:15 13 | })
14 |
> 15 | expectTypeOf(1).toBeVoid()
16 | })
17 | vitest/test/typescript/test-d/test.test-d.ts:49 47 | })
48 |
> 49 | expectTypeOf({ wolk: 'true' }).toHaveProperty('wolk')
50 | |
The floating v1 tag now resolves to a commit (8d5f45db5f, "Skip duplicate S3 cache save when content matches the default-branch fallback", merged 2026-06-30) that skips saving the npm cache under a branch's own lockfile-hash key whenever content matches master's, and relies on a fallback restore that doesn't account for a differing lockfile hash - leaving node_modules empty on any branch that changes package-lock.json (like this one, which adds eslint-plugin-testing-library). v1.7.1 predates that change. Pinning here as a test to confirm this is really the regression before deciding between reverting to a pinned version repo-wide vs. the npm-ci-fallback approach in #7494.
Confirmed on PR #7488 that pinning to v1.7.1 (the release before 8d5f45db5f, "Skip duplicate S3 cache save when content matches the default-branch fallback", merged into the gh-action_cache v1 branch on 2026-06-30) resolves the cache-miss/"tsx: not found" failure directly, without needing a fallback install step. That commit is the actual regression: it skips saving an npm cache entry under a branch's own lockfile-hash key whenever content matches master's, relying on a fallback restore that doesn't account for a differing lockfile hash - so restore-only jobs end up with no node_modules at all whenever a branch's lockfile actually differs from master (e.g. a new devDependency, or a Renovate bump). Pinning to v1.7.1 undoes the regression directly instead of working around its effects.
|
we should probably consider using https://github.com/actions/cache directly instead of relying on sonarsource cache action, like we do with the orchestrator cache. I don't think SonarJS benefits/needs S3 caching done by sonarsource cache |
|
The issue I fixed yesterday was similar to yours but it was for test coverage files cache, and not for node_modules cache. Here is the link to my PR if you want to have a look: #7481 From what I understood by investigating this issue with codex, this is due to the use of the Purpose of this flag is to say "just check that the cache exist but don't read from it and don't save anything in it". Before their fix, it was always reading the cache so we ended up with files available. Now it's breaking because files are not available anymore. In my other PR, I fixed it by removing this flag which does a normal restore, so it's kind of "Read it from the cache, but if it fails then run tests to generate the test coverage file and populate the cache with it" and I've also added an explicit upload artifacts inside jobs that require these artifacts (in my case, only "Next SonarQube Analysis" needed it). All of this to say that downgrading to gh-action_cache is not a good idea because it's not a bug on their side, it's a bug they actually fixed and it will remain like this so there is no upstream fix to expect, this is an issue on our side. If you're in a hurry, I'm ok downgrading to v1.7 for now but we should at least open a ticket to properly fix our CI later. |
lookup-only: true on populate_npm_cache's cache step means the action never actually saves node_modules under the branch key (mode=lookup: "no restore, no save"), same latent bug already fixed for the JS coverage cache in #7481. It only appeared to work when a branch's lockfile hash matched master's, via the fallback-exact-key path. Drop lookup-only so the step does a normal restore-or-install, letting the action's post-step genuinely save under the branch's own key. Downstream jobs (build, test_js, test_js_win, prepare_rspec_rule_data) already do a plain restore against that same key and get a real hit once it's actually populated - no need to pin gh-action_cache back.
|
@erwan-leforestier-sonarsource good catch, and thanks for the pointer to #7481 — you were right, this is the same latent-bug pattern.
Dropped Validated by merging this fix into #7488 (real lockfile diff there): Still agree a tracking ticket for auditing other |
|
Code Review 👍 Approved with suggestions 0 resolved / 1 findingsPins 💡 Quality: PR title/description no longer matches the actual change🔗 link The PR title ("Pin gh-action_cache to v1.7.1") and the summary describe pinning every 🤖 Prompt for agentsOptionsAuto-apply is off → Gitar will not commit updates to this branch. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Gitar |





Summary
prepare_rspec_rule_data(and other jobs that only restore the npm cache) fails withsh: 1: tsx: not foundwhenever a branch'spackage-lock.jsondiffers frommaster's — e.g. any PR adding/bumping a root npm dependency. Reproduced on JS-2014: Implement S8980: react testing library calls should not be wrapped in act() #7480, JS-2015: Implement S8981: regular expressions used in Testing Library queries should not have the global flag #7483, JS-2019: Implement S8985: side effects should not be performed inside "waitFor" callbacks #7488 (each adds a new devDependency for a new rule) and on Update dependency tsx to v4.22.5 #7487 (Renovate bump oftsx).populate_npm_cache's "Cache NPM dependencies" step passedlookup-only: true. Since commit8d5f45db5f("Skip duplicate S3 cache save when content matches the default-branch fallback"), merged intogh-action_cache'sv1branch on 2026-06-30, that flag makes the action pickmode=lookup— no restore, no save, period.populate_npm_cachewas never really saving a branch-scoped cache entry after that; it only appeared to work when a branch's lockfile hash happened to match master's, via the fallback-exact-key path. This is the same latent-bug pattern already fixed for the JS coverage cache in JS-2017 Fix JS coverage handoff for Sonar analysis #7481.Fix: pin— that masked a legitimate upstream fix rather than addressing the actual bug on our side, and wouldn't get an upstream fix since there's nothing to fix upstream.SonarSource/gh-action_cachetov1.7.1lookup-only: truefrompopulate_npm_cache/populate_npm_cache_win's cache step so it does a normal restore-or-install, letting the action's post-step genuinely savenode_modulesunder the branch's own key. Downstream jobs (build,test_js,test_js_win,prepare_rspec_rule_data) already do a plain restore against that same key and get a real hit once it's actually populated.gh-action_cachestays on@v1.package-lock.jsonfrom master). Before the fix,Prepare RSPEC rule datafailed there with the exacttsx: not founderror; after merging this branch in,Populate NPM cache for Linux/Windows,Prepare RSPEC rule data, and all downstream jobs went green.Test plan
Prepare RSPEC rule dataand downstream jobs go green after this fix