Skip to content

Fail fast on package.json/bun.lock drift and stop rewriting non-registry catalog refs - #102

Open
typedrat wants to merge 2 commits into
nix-community:masterfrom
synapdeck:typedrat/hook-drift-detection
Open

Fail fast on package.json/bun.lock drift and stop rewriting non-registry catalog refs#102
typedrat wants to merge 2 commits into
nix-community:masterfrom
synapdeck:typedrat/hook-drift-detection

Conversation

@typedrat

Copy link
Copy Markdown
Contributor

Two fixes to the install hook's offline-prep step, both stemming from the same bun behavior: any drift between package.json and bun.lock makes bun re-resolve the affected dependencies, and re-resolving a git/github/remote-tarball dependency downloads it unconditionally — fatal in the Nix sandbox no matter how complete the cache is. Both were found getting the opencode workspace (~3,200 packages, the reproduction from #77) to install offline, but neither depends on the manifest-cache work in the follow-up PR: the hook doesn't pass --frozen-lockfile, so both failure modes are reachable on master today.

  • Drift detection. Projects routinely commit a bun.lock whose trustedDependencies / patchedDependencies sections lag package.json (opencode's does). Any mismatch makes bun distrust the lockfile mapping wholesale, and the re-resolution surfaces as a wall of ConnectionRefused errors at resolve time with no hint of the cause. The hook's prep script now compares those sections (as a set / as key-value pairs, so pure ordering differences don't count) and fails up front with the actual diagnosis and the fix: refresh bun.lock, commit, regenerate bun.nix. This is deliberately a hard error rather than a silent sync — a lockfile that disagrees with package.json means the dependency cache was generated from something other than what the project declares.
  • Non-registry catalog refs. The catalog: rewrite was converting refs to their resolution even when that resolution is a github/git/tarball/file spec. That substitution itself registers as a changed spec and forces the same network re-resolve. Those refs are now left as catalog: — bun resolves them natively from the lockfile's catalog section.

The prep script also runs whenever bun.lock exists now, not only when it contains catalog: refs, so the drift check covers every project.

Testing: a new flake check (lockfileDriftDetection) runs the prep script against three fixtures — drifted sections must fail naming the drifted entries, identical-but-reordered sections must pass, and a catalog with both a registry and a github ref must rewrite the former and preserve the latter. It's non-vacuous: against the current script it fails with "drifted fixture: expected failure, got exit 0". End-to-end, opencode-as-committed dies in seconds with the drift diagnostic instead of twenty minutes of ConnectionRefused; with its lockfile refreshed it proceeds normally.

Compatibility: builds of projects with a synced lockfile are unaffected. Projects with drifted lockfiles previously failed at resolve time with inscrutable network errors; they now fail earlier with an actionable message. A project whose bun.lock has a non-registry catalog entry was previously broken by the rewrite itself; it now builds.

This is the second of three PRs from getting the full opencode workspace building offline; it stands alone and doesn't depend on the others.

typedrat added 2 commits July 25, 2026 17:51
…network

Two hook fixes for failure modes found installing opencode offline. Both
stem from the same bun behavior: any drift between package.json and
bun.lock makes bun re-resolve the affected dependencies, and re-resolving a
git/github/remote-tarball dependency downloads it unconditionally — fatal
in the sandbox no matter how complete the cache is.

- Detect trustedDependencies / patchedDependencies drift between the root
  package.json and bun.lock and fail with an actionable message (refresh
  bun.lock, regenerate bun.nix). Projects routinely commit a lockfile whose
  copies of these sections lag package.json (opencode's does); without the
  check the drift surfaces as a wall of ConnectionRefused errors at resolve
  time with no hint of the cause.
- Leave catalog: references pointing at non-registry specs (github:, git+,
  tarball URLs, file:) unrewritten. bun resolves those natively from the
  lockfile's catalog section; rewriting them to their resolution registered
  as a changed spec and forced a re-resolve.

The hook now runs the prep script whenever bun.lock exists (previously only
when it contained catalog: refs), so the drift check covers every project.
Runs resolve-catalog.ts against three inline fixtures: drifted
trustedDependencies/patchedDependencies must fail with a diagnostic naming
the drifted entries, identical-but-reordered sections must pass, and
catalog: refs must be rewritten to exact versions except when they resolve
to non-registry specs. The drifted fixture has no catalog: refs, pinning
that the check runs for plain projects too.
@typedrat
typedrat force-pushed the typedrat/hook-drift-detection branch from fda92dc to 150dab2 Compare July 26, 2026 00:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant