Skip to content

The main to development sync can desync package.json from package-lock.json #766

Description

@rubenvdlinde

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:

  1. 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.
  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions