Data

Original research · Repository configuration

The lockfile drift census

How often does a public JavaScript or TypeScript repository have more than one package manager configured inside a single package? We scanned a stratified random sample of 5,371 active public repositories containing a package.json, each frozen at a named commit, and published the raw result.

Headline

3.65%

of repositories carry multiple package-manager configurations in one package — 196 of 5,371, 95% confidence interval 3.18–4.18%.

That means one package has two managers configured at once: a package-lock.json beside a pnpm-lock.yaml, or a lockfile naming a different manager from the "packageManager" field.

Coexistence is not an error. This census does not claim these are mistakes, or that anything should be removed. An extra lockfile can be deliberate — Dependabot coverage, compatibility testing, or a migration in progress. CrossCheck cites configuration evidence; it cannot read maintainer intent, and neither can this census. What the number measures is how often a repository has more than one answer to “which package manager is this?” — a state in which CI, a teammate and a coding agent can each resolve it differently.

Frozen and scanned 21 September 2026 · CrossCheck engine 0.2.0

The numbers

What the sample contains

MeasureRateRepositories95% CI
Multiple package-manager configurations in one package3.65%196 / 5,3713.18–4.18%
Two or more lockfile managers anywhere in the repository5.42%291 / 5,3714.84–6.06%
Declares “packageManager” in package.json22.79%1,224 / 5,37121.69–23.93%
Has a package manager established at all (lockfile or declaration)89.33%4,798 / 5,37188.48–90.13%
Ships an AI-agent instruction file23.55%1,265 / 5,37122.44–24.71%
Has an unparseable package.json0.09%5 / 5,3710.04–0.22%

The first two rows are not nested, and neither contains the other. Two lockfiles in different packages of a monorepo are not counted — a finding is only ever raised inside a single package scope. And the state does not need two lockfiles at all: a single lockfile naming a different manager from the declared "packageManager" is enough.

Of the 196 findings, 182 have two lockfile managers inside one package and 14 come from one lockfile naming a different manager from the one the project declares.

Which managers appear together

The managers that appear together

Managers present togetherFindings
npm + pnpm78
npm + yarn71
bun + npm46
bun + yarn8
pnpm + yarn7
bun + pnpm7
npm + pnpm + yarn2
bun + npm + pnpm2
bun + npm + pnpm + yarn1
bun + npm + yarn1

Of all findings, 147 sit in the repository's root package and 76 inside a sub-package.

Breakdowns

By popularity

StarsRateRepositories95% CI
20-493.58%69 / 1,9282.84–4.50%
50-1493.53%54 / 1,5292.72–4.58%
150-4994.09%41 / 1,0023.03–5.50%
500-14992.29%11 / 4811.28–4.05%
1500-49994.55%12 / 2642.62–7.78%
5000+5.39%9 / 1672.86–9.92%

By language

GitHub languageRateRepositories95% CI
JavaScript2.76%54 / 1,9562.12–3.58%
TypeScript4.16%142 / 3,4153.54–4.88%

TypeScript repositories carry multiple configurations 1.4 percentage points more often than JavaScript ones, and that difference is significant (z = 2.628, p = 0.0086). The census cannot say why; TypeScript repositories skew newer and larger.

By repository age

CreatedRateRepositories95% CI
before 20151.52%4 / 2630.59–3.84%
20150.65%1 / 1550.11–3.56%
20162.08%4 / 1920.81–5.23%
20173.37%9 / 2671.78–6.28%
20181.72%5 / 2910.74–3.96%
20193.31%10 / 3021.81–5.99%
20203.22%13 / 4041.89–5.43%
20213.16%12 / 3801.82–5.44%
20223.88%16 / 4122.40–6.21%
20234.05%18 / 4442.58–6.32%
20244.58%22 / 4803.05–6.84%
20256.84%45 / 6585.15–9.03%
20263.29%37 / 1,1232.40–4.51%

The obvious defence

Does declaring "packageManager" help?

ManifestRateRepositories95% CI
declares "packageManager"4.66%57 / 1,2243.61–5.99%
does not declare it3.35%139 / 4,1472.85–3.94%

Declaring the field goes with a higher rate, not a lower one — 1.31 percentage points (z = 2.14, p = 0.0324).

Do not read that as “declaring it makes things worse”. Declaring the field also makes the state detectable: a lockfile can name a different manager from a declaration that exists, and cannot differ from one that does not. Of the 196 findings, 14 are visible only because the repository declared a manager. The two groups are not measuring the same thing, so this is evidence about detectability, not about hygiene.

The cohort everyone asks about

Repositories that ship AI-agent instructions

CohortRateRepositories95% CI
has agent instruction file4.35%55 / 1,2653.36–5.62%
no agent instruction file3.43%141 / 4,1062.92–4.04%

This difference is not statistically significant. A two-proportion z-test gives z = 1.516, p = 0.1296 — the gap of 0.91 percentage points is within what sampling noise produces at this sample size. On this evidence, repositories that ship an AGENTS.md or CLAUDE.md do not carry multiple package-manager configurations measurably more often than those that do not.

We are stating that plainly because it is the opposite of what a vendor of a tool for this problem would prefer to find. The comparison is also observational: the two groups differ in age, size and activity, none of which is controlled for here. The raw records are published so someone can control for those properly.

What we are not claiming

This is a state, not a defect

Two lockfiles can coexist deliberately and indefinitely without failing anything. The census measures configuration state, not breakage or intent, and it is a snapshot rather than a diff: it cannot tell a state introduced yesterday from one that has been there for three years.

What it can show is which manager a repository's own CI actually installs with. Of the 196 repositories with multiple configurations, 87 (44.39%) have an unconditional install step in their own GitHub Actions workflow, Dockerfile or vercel.json naming one of the managers present. That is context for a maintainer deciding what is intended; it is not a verdict that anything is wrong.

Method

How the sample was built, and why you can check it

The GitHub Search API returns at most 1000 results per query, so “the top N repositories by stars” is a convenience sample. Instead the frame is the union of 145 strata — language × star band × creation date range — of which 129 return fewer than 1000 results and are therefore enumerated completely. That frame holds 71,425 repositories. From it, a proportionally allocated stratified random sample of 6,014 was drawn with a seeded, published PRNG, which makes the draw reproducible rather than merely described.

The honest limit of that design: 16 strata still exceed the 1000-result cap even after splitting, so the frame holds 71,425 of the 79,715 repositories the queries reported, or 89.60%. The shortfall falls entirely on recent, lower-starred repositories, which are therefore under-represented. Every stratum, its reported total and how many were retrieved are published so the gap can be checked rather than taken on trust.

Each repository was cloned at its default branch with a blobless partial clone, its commit SHA recorded, and only the files CrossCheck reads were fetched — selected by the engine's own path filter. Lockfile contents are never downloaded or read, by CrossCheck's design and therefore by ours. The findings come from the shipped engine, not a re-implementation.

Reading blobs rather than a working tree is the one place this departs from an ordinary npx @zfinia/crosscheck run, so it is tested rather than asserted. Scanning a repository both ways — once with the census provider, once with CrossCheck's own provider on a full ordinary clone — gave byte-identical engine input and identical findings in 19 repositories, with 0 disagreements.

Of 6,014 repositories scanned, 6,004 were read successfully and 10 could not be cloned or read; failures are published rather than dropped. Rates use Wilson score intervals at 95%.

To check a single finding you do not need to run anything. Each line of findings.jsonl carries the repository, the frozen commit SHA and a permalink to every cited line.

Check your own repository

The same engine, on your code, in about a second

CrossCheck is free and open source. The CLI needs no account and makes no network calls: it reads configuration files on your machine and names the file and line behind every finding.

npx @zfinia/crosscheck

On pull requests, with nothing to sign up for and no permissions to grant:

- uses: actions/checkout@v5
- uses: zFinia/crosscheck@v0

Compare continuous monitoring plans →

FAQ

What exactly is being counted?

One package with two package managers configured at once — a package-lock.json next to a pnpm-lock.yaml in the same package directory, or a lockfile that disagrees with the “packageManager” field in package.json. A monorepo whose packages each deliberately chose a different manager is not counted: a finding is only ever raised inside a single package scope.

Does this mean the build is broken, or that something is wrong?

No, and the census does not claim that. Two lockfiles can sit in a repository for a long time without failing anything. What it means is that the repository has more than one answer to “which package manager is this?”, so CI, a teammate and an AI coding agent can each install a different dependency tree. Coexistence can also be deliberate — Dependabot coverage or compatibility testing — and CrossCheck cannot read maintainer intent, so nothing here should be read as saying a lockfile ought to be removed.

How were the repositories chosen?

They are a proportionally allocated stratified random sample of a frame of 71,425 public repositories: GitHub-classified JavaScript or TypeScript, at least 20 stars, pushed since 2026-03-21, not archived and not forks. Because the GitHub Search API caps results at 1000 per query, the frame is built as a union of strata that are each enumerated completely, rather than as a “top N by stars” list. The draw is seeded (20260921) and reproducible.

Do repositories that use AI coding agents drift more?

Not measurably, on this evidence. Repositories shipping an AGENTS.md or CLAUDE.md carried multiple package-manager configurations 4.35% of the time versus 3.43% for those without — a gap of 0.91 percentage points that a two-proportion z-test does not find significant (z = 1.516, p = 0.1296). The comparison is observational and uncontrolled for repository age, size and activity, so it cannot settle the question either way; it simply does not show the effect.

Can I check a single finding without running anything?

Yes. The published findings file gives, for every finding, the repository, the exact commit SHA it was frozen at, and a permalink to each cited line. Nothing needs to be installed to verify one.

Is this measuring a real tool or a re-implementation?

The findings come from the shipped CrossCheck engine (0.2.0), not from a re-implementation. The one component written for the census is the file provider, which reads blobs rather than a working tree. It is tested against CrossCheck's own provider on full ordinary clones: 19 repositories produced byte-identical engine input and identical findings, with 0 disagreements.

Licence

The data and this page are published under CC BY 4.0. Cite it as “zFinia, The lockfile drift census, September 2026” and link to this page. The scanner is MIT-licensed.