Measured 2026-09-12 across all 21 core apps, with npm ci itself rather than a proxy, on the toolchain quality.yml pins (Node 24.11.1, npm 11.19.1) and with each repo's own .npmrc present.
| branch |
result |
main, all 21 |
all 21 exit 0 |
development, all 21 |
decidiq and shillinq fail EUSAGE, the other 19 exit 0 |
The desync is created by the sync merge, not by the dependency bumps. main is clean everywhere, including on the two broken apps. The main to beta to development merge took main's package.json and kept development's package-lock.json. Neither side is individually wrong, and the merge has no conflict to report, because the two files are separate paths. Git has no reason to complain and npm has every reason to.
What it costs. One broken install reads as sixteen failing jobs on decidiq and twenty-three on shillinq: eslint, stylelint, build, l10n, unit tests, npm audit, licence, gates. Every one of them is npm ci dying at step one. It also blocks every open PR in the repo, because GitHub Actions checks out the merge of head into base for pull_request events, so each PR inherits the broken base through no fault of its own.
Why a repair cannot just take main's lock. development is legitimately ahead of main on ranges of its own: decidiq carries vite ^8.2.2, vitest ^5.0.0 and webpack ^5.110.3; shillinq carries @conduction/nextcloud-vue ^2.39.0 and @nextcloud/axios ~2.6.0. The lock has to be regenerated against the manifest actually on development (npm install --package-lock-only), which is what the two repairs did: decidiq#1300 and shillinq#1591, both verified with npm ci from a deleted node_modules.
Two things worth fixing here rather than app by app:
- A lockfile-sync check that runs in seconds.
npm ci --dry-run, or equivalent, on every PR and on every sync. It catches this exact failure before it reaches a branch everyone builds on, and it is far cheaper than the sixteen jobs currently discovering it one at a time.
- The sync workflow should regenerate the lock when it carries a
package.json across, or refuse and open an issue. Carrying one half of a pair is the defect.
A measurement note for anyone checking fleet health. Most apps' newest Code Quality run on development reads conclusion: skipped. That is not a dark gate: those are pull_request runs whose head branch is development, which the workflow's if: deliberately excludes. The real signal is the event=push run. Two separate readings of this were wrong today before the right query was found, and skipped looks very close to passed on a dashboard.
Measured 2026-09-12 across all 21 core apps, with
npm ciitself rather than a proxy, on the toolchainquality.ymlpins (Node 24.11.1, npm 11.19.1) and with each repo's own.npmrcpresent.main, all 21development, all 21EUSAGE, the other 19 exit 0The desync is created by the sync merge, not by the dependency bumps.
mainis clean everywhere, including on the two broken apps. Themaintobetatodevelopmentmerge tookmain'spackage.jsonand keptdevelopment'spackage-lock.json. Neither side is individually wrong, and the merge has no conflict to report, because the two files are separate paths. Git has no reason to complain and npm has every reason to.What it costs. One broken install reads as sixteen failing jobs on decidiq and twenty-three on shillinq: eslint, stylelint, build, l10n, unit tests, npm audit, licence, gates. Every one of them is
npm cidying at step one. It also blocks every open PR in the repo, because GitHub Actions checks out the merge of head into base forpull_requestevents, so each PR inherits the broken base through no fault of its own.Why a repair cannot just take
main's lock.developmentis legitimately ahead ofmainon ranges of its own: decidiq carriesvite ^8.2.2,vitest ^5.0.0andwebpack ^5.110.3; shillinq carries@conduction/nextcloud-vue ^2.39.0and@nextcloud/axios ~2.6.0. The lock has to be regenerated against the manifest actually ondevelopment(npm install --package-lock-only), which is what the two repairs did: decidiq#1300 and shillinq#1591, both verified withnpm cifrom a deletednode_modules.Two things worth fixing here rather than app by app:
npm ci --dry-run, or equivalent, on every PR and on every sync. It catches this exact failure before it reaches a branch everyone builds on, and it is far cheaper than the sixteen jobs currently discovering it one at a time.package.jsonacross, or refuse and open an issue. Carrying one half of a pair is the defect.A measurement note for anyone checking fleet health. Most apps' newest Code Quality run on
developmentreadsconclusion: skipped. That is not a dark gate: those arepull_requestruns whose head branch isdevelopment, which the workflow'sif:deliberately excludes. The real signal is theevent=pushrun. Two separate readings of this were wrong today before the right query was found, andskippedlooks very close topassedon a dashboard.