Skip to content

36 manifests have duplicate "engines" keys — and gate-toolchain is structurally blind to it #755

Description

@izzywdev

What

36 package.json files declare "engines" twice. Found incidentally while bumping axios (#754) — npm normalised one manifest down to a single block, which is what exposed it.

$ for f in $(git ls-files '*package.json' | grep -v node_modules); do
    n=$(grep -c '"engines"' "$f"); [ "$n" -gt 1 ] && echo "$n x $f"; done
All 36

FuzeQuality, api-client, apps-client, backend, backend/applications, backend/core, backend/security, billing-client, clock-app, custom-hostname-client, design-system, frontend, packages/auth, packages/billing-ui, packages/chat-client, packages/chat-ui, packages/config-ui, packages/feature-flags, packages/i18n, packages/i18n-translate, packages/onboarding-kit, packages/portal-admin-ui, packages/security, packages/selection-lists-ui, portal-client, sdk, services/billing-service, services/chat-service, services/email-service, services/notification-service, services/payment-service, services/provisioning-service, services/sms-service, shared, tests/e2e/billing-invoices, tests/selection-list-service

Why it is not currently breaking

Every duplicate pair is identical (node: ">=24.0.0", npm: ">=10.0.0"), so whichever copy a parser picks, the value is the same. Nothing misbehaves today.

Why it still matters

gate-toolchain cannot detect this. It does JSON.parse, which silently keeps the last occurrence — so the gate sees one well-formed value and passes. The gate whose entire job is policing engines is blind to engines being declared twice.

The hazard is divergence. The moment the two copies disagree — a partial sweep, a bad merge resolution, a tool that rewrites only the first block — behaviour depends on which copy the reader takes:

  • JSON.parse (the gate, most tooling) → last
  • some streaming/strict parsers → first, or reject outright
  • npm → normalises to one, silently discarding the other

That is precisely the failure shape this repo's CLAUDE.md already documents twice: a check that passes while the underlying state is wrong. Duplicate keys are legal enough that nothing complains, and wrong enough that they will eventually bite.

Likely origin

The #646/#647 sweep added engines after "version" in manifests that lacked it. These files appear to have received a second block appended by another pass over the same worklist. Worth confirming from history rather than assuming — I have not traced it.

Suggested fix

  1. De-duplicate — collapse each to a single block. Mechanical; npm pkg fix, or a scripted rewrite, or letting npm normalise each manifest.
  2. Teach gate-toolchain to catch it. Parse the raw text for repeated top-level keys instead of relying solely on JSON.parse. Worth generalising beyond engines — any duplicated top-level key in a manifest is a latent version of this bug.
  3. Consider whether gate-line-endings or a sibling formatting gate is the more natural home for a "manifests are well-formed" check.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions