Skip to content

fix(lockfile): a declaration resolves ambiguity, whatever it names - #647

Merged
colinhacks merged 2 commits into
mainfrom
fix/lockfile-ambiguity-remedy
Aug 1, 2026
Merged

fix(lockfile): a declaration resolves ambiguity, whatever it names#647
colinhacks merged 2 commits into
mainfrom
fix/lockfile-ambiguity-remedy

Conversation

@colinhacks

@colinhacks colinhacks commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Two changes to the same error.

Behavior. detect.rs's module docs have always promised a declaration naming the running tool "never makes a multi-lockfile project ambiguous". The code contradicted them, so a declared project with two lockfiles refused to install. That was inescapable, not just strict: nub install writes devEngines.packageManager: nub itself, so any stray second lockfile broke every later install and the suggested remedy was already applied. A self-name declaration now resolves like any other — canonical lockfile wins, else precedence. Undeclared with two lockfiles is still a hard error, unchanged.

Message. The ambiguity remedy no longer advises setting a declaration; declaration-mismatch keeps its wording, where the advice is correct.

Found on Cloudflare Workers Builds, which manufactures the state by running its own install (bun) beside a committed nub.lock.

Docs: corrects two claims that a declaration resolves ambiguity, replaces an invented mockup line with captured output, drops a false .tool-versions warning on the Cloudflare page, and documents SKIP_DEPENDENCY_INSTALL.

Caveat: resolving past a stray exposes it to parsing, so a malformed stray now fails on its own merits rather than as an identity error.

Refs #473

…nnot help

A declaration naming nub is a self-name: the engine reads it as "preserve
whatever format this project already uses", which settles nothing when two
candidate lockfiles are present. Since `nub install` writes that declaration
itself, telling an ambiguous project to declare its manager sent the common
case in a circle.

Splits the shared remedy so the ambiguity error names an action that actually
resolves it, and leaves the declaration-mismatch remedy unchanged, where
setting the declaration is still correct.

Corrects two docs claims that said the same untrue thing, and replaces the
mockup's invented help line with real captured output.

Also drops a warn callout on the Cloudflare page claiming .tool-versions is
unsupported there. It is supported: Workers Builds announces it, provisions a
pinned nub version on demand, and nub reads the same file. Documents
SKIP_DEPENDENCY_INSTALL, which is how a nub-owned project avoids the second
lockfile the builder's own install creates.

Refs #473
Copilot AI review requested due to automatic review settings August 1, 2026 01:41
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nub Ready Ready Preview Aug 1, 2026 2:53am

Request Review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adjusts Nub’s lockfile-ambiguity guidance so nub install no longer tells nub-declared projects to “set a declaration” that can’t resolve the ambiguity, and updates docs to reflect the corrected remedy (including Cloudflare Workers Builds guidance).

Changes:

  • Split the lockfile-identity error help text so ambiguity and declaration-mismatch have different (correct) remedies.
  • Added a regression test covering the nub-declared + multiple-lockfile ambiguity case.
  • Updated install + Cloudflare deployment docs to remove the misleading declaration advice and document SKIP_DEPENDENCY_INSTALL.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
site/content/docs/install/index.mdx Updates ambiguity explanation and captured error output to avoid recommending a nub self-declaration as a fix.
site/content/docs/deployment/cloudflare.mdx Documents Cloudflare Workers Builds behavior, SKIP_DEPENDENCY_INSTALL, and version pinning via .tool-versions.
crates/nub-cli/tests/pm_identity.rs Adds regression test ensuring ambiguity help doesn’t advise “set the declaration” for nub-declared projects.
crates/nub-cli/src/pm_engine/mod.rs Splits ambiguity vs mismatch remedies in identity_error help text.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- **lockfile on disk**

In a workspace, the chain runs from any member: Nub walks up to the root, which carries the declaration and lockfile. Two lockfiles for different managers, with no declaration, is a hard error.
In a workspace, the chain runs from any member: Nub walks up to the root, which carries the declaration and lockfile. Two lockfiles for different managers is a hard error unless a declaration names one of them. Declaring Nub itself does not settle it: Nub preserves whatever format the project already uses, so with two candidates there is nothing to choose between.
Comment on lines +101 to +102
nodejs 24.18.0
nub 0.6.0

@pullfrog pullfrog Bot left a comment

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.

Important

The Rust half is sound, but the Cloudflare page's new .tool-versions pinning advice replaces an accurate warning with claims no published source supports — and the sub-claim that an asdf short name nub resolves is provably not in place yet.

Reviewed changes — the full diff at c4bb338: one Rust behavior change, one new integration test, and two docs pages.

  • Split remedy stringidentity_error now carries a separate AMBIGUITY_REMEDY (remove the stale lockfile, or run nub pm use <pm> naming a specific manager) while DeclarationMismatch keeps the old wording. No control-flow change.
  • Premise verifieddetect.rs:230 filters the declaration through is_self_name, and nub's profile sets self_names: &["nub"] plus canonical_lockfile_always_wins: false (identity.rs:155,185), so a nub-declared project with two lockfile families really does land on AmbiguousLockfiles. nub install does stamp that declaration itself (install_family.rs:1218).
  • Regression test can fail — the new test negates "set the declaration", which the pre-fix shared REMEDY contained, so it pins the actual behavior rather than passing vacuously.
  • Install docs — the corrected ambiguity claim and the nub-self-name caveat match the engine's decision table.
  • Cloudflare docsSKIP_DEPENDENCY_INSTALL checks out against Cloudflare's build-image page (1 or true). The .tool-versions / asdf section does not; see the inline comments.

ℹ️ The long-term answer for the hosted-builder state is still open

The message is better, but the state itself remains a hard error by design: nub's profile deliberately sets canonical_lockfile_always_wins: false, so a committed nub.lock beside a builder-written bun.lock cannot resolve on its own. This PR routes users to a build variable, which works only on builders that expose one. Worth deciding explicitly whether the build-var workaround is the permanent story, or whether nub.lock beside exactly one foreign lockfile should eventually resolve to nub.lock — that call belongs to you, not to a follow-up review.

ℓ Nitpicks

  • site/content/docs/pm/index.mdx:24 still offers "declare the pin with [nub pm use]" as the ambiguity fix, without the nub-self-name caveat this PR adds to install/index.mdx. Same claim, one page over.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment on lines +98 to +105
Workers Builds provisions its tools with asdf, so a project-root `.tool-versions` pins Nub and Node together:

```text title=".tool-versions"
nodejs 24.18.0
nub 0.6.0
```

A pinned version the image doesn't already carry is downloaded during the build, so the pin holds even when it differs from the image default. Nub reads the same file when resolving its own Node, which keeps the two in agreement from one place.

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.

No published source supports this, and one sub-claim is refutable today: the asdf short name nub does not resolve — asdf-vm/asdf-plugins/plugins/nub 404s and the registry PR is still open (per #473) — so nub 0.6.0 only works if Cloudflare wired the plugin into its own fixed set. Cloudflare's build-image page documents version files for .nvmrc/.node-version/.python-version/.ruby-version only, never .tool-versions, and lists no nub row at all.

Technical details
# Cloudflare `.tool-versions` pinning is asserted, not verified

## Affected sites
- `site/content/docs/deployment/cloudflare.mdx:98-105` — asserts asdf provisioning, a `nub 0.6.0` pin, and "a pinned version the image doesn't already carry is downloaded during the build".
- `site/content/docs/deployment/cloudflare.mdx:22` — adds `.tool-versions` to the Node-version source list for this image.
- The deleted `<Callout type="warn">` said Workers Builds does not document `.tool-versions`, which still matches both Cloudflare's page and #473's own wording ("not `.tool-versions`, and there's no plugin mechanism").

## Evidence
- https://developers.cloudflare.com/workers/ci-cd/builds/build-image/ (updated 2026-07-30): Runtime table's File column is `.nvmrc, .node-version` for Node.js; the Tools-and-languages table has no File column at all; "asdf" appears nowhere; nub appears nowhere. `SKIP_DEPENDENCY_INSTALL` = `1 or true` IS documented, so the earlier section in this diff is fine.
- `https://raw.githubusercontent.com/asdf-vm/asdf-plugins/master/plugins/nub` → 404. Registry PR `asdf-vm/asdf-plugins#1170` still open.
- https://www.codejam.info/2026/02/cloudflare-workers-choke-asdf-tool-versions.html (2026-02-10): Workers/Pages read `.tool-versions` undocumented and fail on a bare `nodejs 24.13.1` line (`Failed: error occurred while installing tools or dependencies`), and reports `SKIP_DEPENDENCY_INSTALL` does not suppress that handling — which interacts with this diff's other new section, since a reader following both adds a `.tool-versions` AND sets that variable.
- Counter-evidence: #473 carries a Cloudflare engineer's note that nub was being added to the image's plugin set around 2026-07-30, so the claim may well be true in practice. #473's own step 3 required confirming `.tool-versions` provisioning end-to-end on a real Workers build BEFORE documenting it.

## Required outcome
Either cite/perform the real Workers Builds run that proves a `.tool-versions` `nub` pin provisions the requested version, or drop the section back to the devDependency pin and keep a caveat that `.tool-versions` is undocumented on this image.

Cloudflare runs an optional build command and then a Wrangler deploy command. Point the build command at the package script that uses Nub; the preinstalled binary means no devDependency is required.

The image currently defaults to Node 24.18.0 and also preinstalls Node 22.23.2. Use `.node-version` or `.nvmrc` to select a Node version that both Cloudflare and Nub read.
The image currently defaults to Node 24.18.0 and also preinstalls Node 22.23.2. Use `.node-version`, `.nvmrc`, or `.tool-versions` to select a version that both Cloudflare and Nub read.

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.

Same evidence as above: Cloudflare's build-image page lists .nvmrc and .node-version as the Node version files and never mentions .tool-versions, so "a version that both Cloudflare and Nub read" holds for the first two but not the third. Nub reading .tool-versions (nub-core/src/node/discovery.rs:499) is not in question — Cloudflare's side is.

const MISMATCH_REMEDY: &str =
"set the declaration: nub pm use <pm> — or remove the stale lockfile";
const AMBIGUITY_REMEDY: &str =
"remove the stale lockfile, or run nub pm use <pm> naming a specific manager";

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.

"naming a specific manager" is broader than what works: plan_alignment only clears the ambiguity when that manager's lockfile is already on disk (use_align.rs:114-131), and otherwise bails at use_align.rs:148 with its own remove-the-stale-ones-first message. In this PR's own new fixture (package-lock.json + yarn.lock), nub pm use nub is one of the targets that bails. Scoping the wording to a manager that owns one of the lockfiles found would make the second clause unconditionally true.

Suggested change
"remove the stale lockfile, or run nub pm use <pm> naming a specific manager";
const AMBIGUITY_REMEDY: &str =
"remove the stale lockfile, or run nub pm use <pm> naming one of the managers found";

The module docs for detect.rs have always promised that a declaration
naming the running tool "never makes a multi-lockfile project ambiguous".
The code 230 lines below said the opposite and won, so a declared project
with two lockfiles refused to install.

That made the state inescapable rather than merely strict: `nub install`
writes `devEngines.packageManager: nub` itself, so once anything dropped a
second lockfile beside nub.lock — a hosted builder running its own install
is the common case — every later install refused, and the suggested remedy
was already applied.

A self-name declaration now resolves like any other: the canonical lockfile
wins when present, else the highest-precedence candidate. An undeclared
project with two lockfiles is still a hard error, unchanged.

Note this exposes a stray lockfile to parsing that the ambiguity check
previously short-circuited: a malformed stray now fails on its own merits
rather than as an identity error.

Refs #473
@colinhacks colinhacks changed the title fix(pm): ambiguity remedy no longer advises a declaration that cannot help fix(lockfile): a declaration resolves ambiguity, whatever it names Aug 1, 2026

@pullfrog pullfrog Bot left a comment

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.

Caution

a5cca4a turns the message fix into a behavior change, and the state it makes reachable is one every lockfile reader still answers differently. In a nub-declared project with nub.lock beside a foreign lockfile, nub install now writes nub.lock while reading the foreign one — under --frozen-lockfile it installs the stray's graph outright. That is the exact Cloudflare state the PR is about, so a loud error becomes a silent wrong install.

Reviewed changes — the delta from c4bb338 to a5cca4a. The Rust remedy strings, both docs pages, and the three open threads from the prior review are untouched by this commit.

  • Made a self-name declaration resolve a multi-lockfile projectdetect.rs's undeclared branch now rescues Err(AmbiguousLockfiles) whenever a declaration is present, returning the canonical lockfile when it exists and the highest-precedence candidate otherwise.
  • Documented a new decision-table row — module docs gained | self | two or more PMs' | Existing(canonical, else precedence) | plus the claim that only an undeclared project is ambiguous.
  • Added two detect.rs unit tests — canonical-wins, and the no-canonical precedence fallback.
  • Repurposed the pm_identity.rs regression testa_nub_declared_ambiguous_project_is_not_told_to_set_a_declaration became a_nub_declared_project_resolves_past_a_stray_lockfile, now asserting exit 0 on nub.lock + a real bun.lock.

⚠️ canonical_lockfile_always_wins: false is now half-true, and which half survives is your call

The NUB profile's own docs say nub.lock "never silently outranks a foreign lockfile beside it; that state is the loud ambiguity/contradiction error" (identity.rs:102-105), and lock_yaml_is_nub_identity_and_conflicts_are_loud's docstring says the same. After this commit that holds only for undeclared projects — and since nub install stamps the declaration itself, the declared case is the common one. Two end states are coherent; the middle one this commit lands on is the only one that splits the resolver's answer from every reader's.

Technical details
# The strict-identity posture needs an explicit decision, not a partial exemption

## Affected sites
- `crates/nub-cli/src/pm_engine/identity.rs:102-105` — the profile doc for `canonical_lockfile_always_wins` states the opposite of the new resolver behavior.
- `crates/nub-cli/tests/pm_identity.rs:493-498` — the same claim in a test docstring ("never a silent winner").
- `vendor/aube/crates/aube-lockfile/src/detect.rs:68-80``canonical_lockfile_always_wins`'s own doc comment describes the flag as making the state "the loud `Error::AmbiguousLockfiles` / `Error::DeclarationMismatch` instead of a silent win".

## Required outcome
- One documented posture that the resolver, the raw candidate order, and these three doc sites all agree on.

## Suggested approach (optional)
- **Keep strict identity:** leave the flag `false`, keep the declared-plus-stray state loud, and address the hosted-builder case at the build level only (`SKIP_DEPENDENCY_INSTALL`, which the Cloudflare page already documents).
- **Or flip the flag to `true`:** `lockfile_candidates` then leads with the canonical entries, so the raw readers and the declaration-aware resolver agree with no threading work, and this new arm is largely redundant. The cost is that the *undeclared* `nub.lock` + foreign state stops being loud, which `pm_identity.rs:514-529` currently pins.

## Open questions for the human
- Should the resolved-past lockfile be named in a `WARN_NUB_*`? Today it is ignored in silence, and on a hosted builder it is regenerated on every build, so nothing ever tells the user the file is inert.

ℹ️ Nitpicks

  • a_self_name_declaration_without_the_canonical_file_falls_to_precedence asserts only matches!(…, Existing(_)). The test names precedence, so assert_eq!(resolve(&d).unwrap(), ResolvedLockfileKind::Existing(LockfileKind::Pnpm)) would actually pin it — moot if the fallback branch goes away.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

// running its own install is the common case — every later install
// refused, and the suggested remedy (declare a manager) was
// already done.
Err(Error::AmbiguousLockfiles { found }) if declared.is_some() => {

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.

This makes a state reachable that every lockfile reader still answers differently. resolve_project_lockfile_kind returns Existing(Aube) here, but parse_lockfile_with_kind_and_options walks lockfile_candidates, which puts the canonical entries last under canonical_lockfile_always_wins: false — so under nub the first existing candidate is the foreign lockfile. nub install therefore writes nub.lock and reads bun.lock.

Under --frozen-lockfile (the CI default) that installs the builder's graph and drift-checks against it; in the default mode it seeds the resolver from it and stamps those pins into nub.lock. The committed lockfile is ignored either way.

Technical details
# The declaration-aware resolver and the raw lockfile readers disagree in the newly-reachable state

## Affected sites
- `vendor/aube/crates/aube-lockfile/src/detect.rs:277-290` — returns `Existing(Aube)` for self-name declaration + `nub.lock` + foreign lockfile. Previously `Err(AmbiguousLockfiles)`, so the state aborted before any reader ran.
- `vendor/aube/crates/aube-lockfile/src/io.rs:590-603``lockfile_candidates` appends the Aube entries AFTER the foreign ones when `canonical_lockfile_always_wins == false` (`crates/nub-cli/src/pm_engine/identity.rs:185`).
- `vendor/aube/crates/aube-lockfile/src/io.rs:495-510``parse_lockfile_with_kind_and_options` returns the first existing candidate: the foreign lockfile.
- `vendor/aube/crates/aube/src/commands/install/resolve.rs:504``FrozenMode::Frozen` parses through that path, so `check_drift_workspace_for_kind` and the installed graph both come from the foreign lockfile.
- `vendor/aube/crates/aube/src/commands/install/resolve.rs:30``pre_parse_lockfile` feeds `existing_for_resolver` (`install/mod.rs:1102-1107`) in the default Prefer mode, so the stray's pins are reused and written into `nub.lock` (write target from `install/mod.rs:1041``settings_context.rs:473`, which IS declaration-aware).
- `vendor/aube/crates/aube-lockfile/src/io.rs:341-351``active_lockfile_has_conflict_markers` inspects the foreign file, so a real conflict left in `nub.lock` is missed.
- Same raw primitive, further consequences: `install/workspace.rs:428` (`sharedWorkspaceLockfile=false` writes a member lockfile in the FOREIGN format), `add/supply_chain.rs:64-81` (allowlist built from the stray's package names), `commands/fetch.rs:53`, `runtime.rs:754-773` (`devEngines.runtime` pin silently dropped), `install/settings.rs:702-717` (`packageExtensionsChecksum` not stamped, which a later frozen run expects).

## Required outcome
- In the resolved state, the file the install READS is the same file it writes — for the frozen path, the resolver-reuse baseline, and conflict-marker detection alike.
- The behavior is pinned by a test that fails when the two disagree.

## Suggested approach (optional)
- Thread the already-computed resolved kind into the read path (a `parse_lockfile_of_kind`-style entry point, or pass `source_kind_before` into `pre_parse_lockfile` and the `FrozenMode::Frozen` parse) so the declaration governs reading as well as writing.
- Or flip `canonical_lockfile_always_wins` to `true` for the NUB profile, which reorders `lockfile_candidates` itself and makes both sides agree without threading — see the review body for what that costs.

Comment on lines +278 to +289
match existing
.iter()
.find(|(_, k)| *k == LockfileKind::Aube)
.or_else(|| existing.first())
{
Some((path, kind)) => Ok(ResolvedLockfileKind::Existing(refine_yarn_kind(
path, *kind,
))),
// Unreachable in practice (an ambiguity implies at least
// two candidates); propagate rather than panic.
None => Err(Error::AmbiguousLockfiles { found }),
}

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.

.or_else(|| existing.first()) picks between two foreign families by filename precedence. A self-name declaration means "preserve whatever format this project already uses" — with pnpm-lock.yaml and yarn.lock both present there is no such format, which is what the comment deleted just above this said. The hosted-builder state that motivates the PR always has the canonical file, so nothing in scope needs this branch.

Suggested change
match existing
.iter()
.find(|(_, k)| *k == LockfileKind::Aube)
.or_else(|| existing.first())
{
Some((path, kind)) => Ok(ResolvedLockfileKind::Existing(refine_yarn_kind(
path, *kind,
))),
// Unreachable in practice (an ambiguity implies at least
// two candidates); propagate rather than panic.
None => Err(Error::AmbiguousLockfiles { found }),
}
match existing.iter().find(|(_, k)| *k == LockfileKind::Aube) {
Some((path, kind)) => Ok(ResolvedLockfileKind::Existing(refine_yarn_kind(
path, *kind,
))),
// No canonical lockfile among the candidates: a self-name
// declaration accepts every format aube can preserve, so
// with two there is no fact of the matter which one.
None => Err(Error::AmbiguousLockfiles { found }),
}

//! | self | two or more PMs' | `Existing(canonical, else precedence)` |
//!
//! The last row is why a declaration is worth setting: only an
//! *undeclared* project is ambiguous. A declaration — whether it names

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.

"always resolves" overstates it. A declaration naming a tool declared_lockfile_kind doesn't know (vlt, deno) passes the is_self_name filter at line 235, gets None, and goes straight to resolve_undeclared at line 257 — so it still returns AmbiguousLockfiles, with the new arm never consulted. Worth qualifying to self-names and known managers, since a docs-versus-code mismatch in this very paragraph is what the PR set out to fix.

/// The two states get DIFFERENT remedies because "set the declaration" is a
/// dead end for one of them. A declaration naming nub is a self-name: the
/// engine reads it as "preserve whatever format this project already uses"
/// (`aube-lockfile`'s `is_self_name` carve-out), which resolves nothing when

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.

"which resolves nothing when two candidate lockfiles are present" is precisely what a5cca4a reversed, so this paragraph now documents the pre-fix engine. The remedy split still earns its keep — ERR_NUB_LOCKFILE_AMBIGUOUS can now only reach an undeclared or foreign-declared project — but the stated reason needs rewriting to that.

- **lockfile on disk**

In a workspace, the chain runs from any member: Nub walks up to the root, which carries the declaration and lockfile. Two lockfiles for different managers, with no declaration, is a hard error.
In a workspace, the chain runs from any member: Nub walks up to the root, which carries the declaration and lockfile. Two lockfiles for different managers is a hard error unless a declaration names one of them. Declaring Nub itself does not settle it: Nub preserves whatever format the project already uses, so with two candidates there is nothing to choose between.

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.

"Declaring Nub itself does not settle it" is now the opposite of the engine's behavior: detect.rs:277 resolves a self-name declaration to nub.lock, else to the highest-precedence candidate. Line 215's "Two lockfiles it cannot choose between" lead-in and line 227's hosted-builder sentence carry the same stale claim — the ambiguity error now requires no declaration.

</Callout>
## Letting Nub run the install

Workers Builds installs dependencies itself before the build command runs, picking the package manager from the lockfile it finds. It does not yet recognize `nub.lock`, so a Nub-owned project ends up with a second lockfile from whichever manager Cloudflare falls back to. The next `nub install` then refuses, unable to tell which one owns the project.

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.

"The next nub install then refuses" no longer happens for a Nub-owned project: that project carries the devEngines.packageManager: nub declaration nub install stamps, which the new resolver arm resolves. SKIP_DEPENDENCY_INSTALL is still the right advice — the reason is now that Cloudflare's install is wasted work and leaves a stray lockfile behind that its next build picks up again.

"a declaration resolves ownership — no ambiguity: {stderr}"
);
assert!(
dir.join("nub.lock").is_file(),

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.

nub.lock is a file because the test wrote it two lines up, and nothing on this path deletes it — the assertion holds even if the install resolved against bun.lock. Together with assert_eq!(code, 0) that means the test passes under the read/write divergence flagged in detect.rs. Giving the two lockfiles different pins for one dependency and asserting the installed version came from nub.lock would make it able to fail.

@colinhacks
colinhacks merged commit 7ec1893 into main Aug 1, 2026
66 checks passed
@colinhacks
colinhacks deleted the fix/lockfile-ambiguity-remedy branch August 1, 2026 03:18
@colinhacks

Copy link
Copy Markdown
Contributor Author

Shipped in v0.7.0: https://github.com/nubjs/nub/releases/tag/v0.7.0

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.

2 participants