Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ jobs:
with:
node-version: 22

# Runs before `npm install` because npm 11 silently strips the `libc`
# field from optional-dependency lockfile entries (see #1160). If the
# check ran post-install we'd flag every CI run instead of the PRs that
# actually introduce the regression.
- name: Verify lockfile libc discriminators
run: node scripts/verify-lockfile-libc.mjs

- name: Install dependencies
timeout-minutes: 20
shell: bash
Expand Down
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 47 additions & 0 deletions scripts/verify-lockfile-libc.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env node
// Verifies that @optave/codegraph-linux-* entries in package-lock.json declare
// the `libc` discriminator. npm 11 silently strips this field when generating
// the lockfile on non-Linux hosts (and sometimes on Linux too), even though the
// published packages declare it. Without it, npm cannot disambiguate
// linux-x64-gnu vs linux-x64-musl when resolving from the lockfile and may
// install (or load) the wrong native binary on Alpine/musl hosts.
//
// Run via `npm run lint` (or directly) in CI to catch silent regressions from
// Dependabot bumps and contributor `npm install` runs.
import { readFileSync } from 'node:fs';

const EXPECTED_LIBC = {
'@optave/codegraph-linux-arm64-gnu': 'glibc',
'@optave/codegraph-linux-x64-gnu': 'glibc',
'@optave/codegraph-linux-x64-musl': 'musl',
};
Comment on lines +15 to +19
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 arm64-musl not covered by the verifier

The CI parity job's inline Node script already maps linux-arm64-musl@optave/codegraph-linux-arm64-musl, suggesting the package is expected to ship. If that package is added to the lockfile in a future PR, the verifier won't catch npm 11 stripping its libc: ["musl"] field — the same silent regression this PR is designed to prevent. Adding it to EXPECTED_LIBC now (even if the package is currently missing from the lockfile, the missing from package-lock.json branch handles that gracefully) would make the guard future-proof.

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good future-proofing thought, but adding @optave/codegraph-linux-arm64-musl to EXPECTED_LIBC today would actually break CI: the package isn't in package.json's optionalDependencies yet, so it's absent from package-lock.json, and the current verifier treats a missing entry as a hard failure (failures.push(${pkgName}: missing from package-lock.json)), not a graceful skip.

The CI parity-job mapping references the package optimistically, but it's not actually shipped today. I've opened #1168 to track adding it to EXPECTED_LIBC the moment it lands in optionalDependencies, and to consider whether the "missing" branch should be relaxed to a warn-and-skip so future additions can be made future-proof without immediately failing.


const lock = JSON.parse(readFileSync('package-lock.json', 'utf8'));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 CWD-relative lockfile path breaks non-root invocations

readFileSync('package-lock.json', 'utf8') resolves against the process working directory. Running node scripts/verify-lockfile-libc.mjs from the scripts/ subdirectory (a natural mistake) throws ENOENT: no such file or directory, open 'package-lock.json' with no hint of the real problem. In CI this is fine since the step always runs from the repo root, but a contributor running it locally from the wrong directory gets a confusing error rather than a clear failure message.

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in b72df7e. The script now resolves package-lock.json relative to import.meta.url so it works from any CWD (repo root, scripts/ subdirectory, or anywhere else). Verified manually from all three locations.

const failures = [];

for (const [pkgName, expectedLibc] of Object.entries(EXPECTED_LIBC)) {
const entry = lock.packages?.[`node_modules/${pkgName}`];
if (!entry) {
failures.push(`${pkgName}: missing from package-lock.json`);
continue;
}
const libc = entry.libc;
if (!Array.isArray(libc) || !libc.includes(expectedLibc)) {
failures.push(
`${pkgName}: expected libc=["${expectedLibc}"], got ${JSON.stringify(libc)}`,
);
}
}

if (failures.length > 0) {
console.error('package-lock.json libc discriminator check failed:\n');
for (const f of failures) console.error(` - ${f}`);
console.error(
'\nnpm install may have stripped the libc field. Restore it by editing\n' +
'package-lock.json so each @optave/codegraph-linux-* entry includes\n' +
'its libc field (see expected values above). Tracked in #1160.',
);
process.exit(1);
}

console.log('package-lock.json libc discriminators OK');
Loading