← reports

#4768 · Runtime installer requires registry URL metadata

BugPriority: MediumEffort: Lowplugins · confirmed-reproGitHub issueOctober 2, 2026 · base 129f621771a3e275773992db648316966ac207cf

Verdict: REPRODUCED · Root-cause confidence: high

1. TL;DR

A first-time Browser Automation runtime install fails if the host's npm configuration suppresses registry URLs in lockfiles. The installer inherits the host's npm configuration through HOME but assumes the installed package's hidden lockfile always contains a resolved URL. Real npm successfully installs and audits the pinned package, then the installer rejects the absent URL while parsing the lockfile. Two separate clean checkouts reproduced that exact failure. Explicitly retaining URL metadata for this isolated install repairs the mismatch without making the validation optional.

2. Claims vs findings

ClaimFindingEvidence
Suppressing registry URL metadata breaks a cold runtime install.VerifiedTwo real npm cold installs failed with the same missing-resolved Zod error.
The pinned registry, provenance, and checksum checks can remain strict.VerifiedWith the one-line installer change, real npm audit, provenance, pinned checksum, and binary version verification all succeeded.
A verified warm runtime bypasses this failing installation path.VerifiedThe extended owner-boundary test reuses the runtime with npm absent and the download URL unreachable, for both configuration cases.
The specific reported macOS/npm combination fails.Not directly testedThe reproduced environment is Linux x86_64, Node 22.19.0, npm 10.9.3. The failing code path is platform-independent.

3. Environment

4. Minimal reproduction

  1. Clone trusted upstream and select the recorded main commit:
    git clone https://github.com/get-bb/bb.git bb-4768
    cd bb-4768
    git checkout --detach 129f621771a3e275773992db648316966ac207cf
    pnpm install --frozen-lockfile --prefer-offline
    pnpm exec turbo run build
  2. Save the reproduction harness as plugins/browser-automation/installer.issue-4768.repro.ts. It imports the real installer and trusted release pin; it does not mock npm or execute code from the issue.
  3. Run:
    pnpm --filter bb-plugin-browser-automation exec tsx installer.issue-4768.repro.ts

Expected: successful verified installation and exit 0. Actual: exit 1 after npm signature verification, with this output:

locating npm on the browser host
installing dev-browser@1.0.0-rc.3 with npm
verifying npm registry signature and provenance
[
  {
    "expected": "string",
    "code": "invalid_type",
    "path": [
      "packages",
      "node_modules/dev-browser",
      "resolved"
    ],
    "message": "Invalid input: expected string, received undefined"
  }
]
undefined
<first-checkout>/plugins/browser-automation:
 ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL  Command failed with exit code 1: tsx installer.issue-4768.repro.ts

Reproduction harness

import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { installRuntime, currentPlatform } from "./installer.js";
import { runtimeRelease } from "./runtime-pin.js";

const fixture = await mkdtemp(join(tmpdir(), "bb-4768-repro-"));
const platform = currentPlatform();
if (platform === null) throw new Error("Unsupported runtime platform");
await writeFile(join(fixture, ".npmrc"), "omit-lockfile-registry-resolved=true\n");
try {
  const result = await installRuntime({
    release: runtimeRelease,
    dataDir: join(fixture, "data"),
    platform,
    signal: AbortSignal.timeout(180_000),
    env: { PATH: process.env.PATH, HOME: fixture },
    onProgress: console.log,
  });
  console.log("SUCCESS", result.version, result.sha256);
} catch (error) {
  console.error(error instanceof Error ? error.message : String(error));
  process.exitCode = 1;
} finally {
  await rm(fixture, { recursive: true, force: true });
}

For the network-free regression, apply regression.diff to the recorded base and run:

pnpm exec turbo run test --filter=bb-plugin-browser-automation -- installer.test.ts

Expected red result: 1 failed / 10 passed. Only the registry-URL-omission case fails, with the same Zod error at installer.ts:630. The original success test is parameterized rather than duplicated. The existing child-process npm fixture is extended to model the omission observed with real npm.

5. Root cause

installEnvironment retains HOME, so npm reads the isolated user's configuration. The npm install arguments pin the registry and disable lifecycle scripts, but do not require registry URL metadata. Consequently npm's hidden lockfile omits resolved when its omission setting is enabled.

The installedLockSchema requires resolved: z.string(). After successful npm signature verification, installRuntime parses that lockfile before asserting the URL belongs to the pinned registry and validating provenance against the installed tarball's integrity. Missing metadata therefore fails schema validation before provenance and binary download. This is an input-generation/validation contract mismatch, not an invalid signature or failed artifact download.

6. Proposed fix and verification

Pass --omit-lockfile-registry-resolved=false to this installer-owned npm install invocation. Preserve the required lockfile schema, pinned-registry URL check, signature audit, provenance binding, artifact digests, executable permissions, and version probe unchanged. No repository dependency, release packaging, public contract, or security boundary changes are needed.

The local patch passed all 11 installer tests; all 82 Browser Automation tests in 8 files; and the plugin's Turbo lint/typecheck tasks. The real cold installation then completed under the same omission-enabled temporary HOME:

locating npm on the browser host
installing dev-browser@1.0.0-rc.3 with npm
verifying npm registry signature and provenance
downloading dev-browser-linux-x64 for 1.0.0-rc.3
downloading dev-browser-linux-x64: 0%
downloading dev-browser-linux-x64: 10%
downloading dev-browser-linux-x64: 20%
downloading dev-browser-linux-x64: 30%
downloading dev-browser-linux-x64: 40%
downloading dev-browser-linux-x64: 50%
downloading dev-browser-linux-x64: 60%
downloading dev-browser-linux-x64: 70%
downloading dev-browser-linux-x64: 80%
downloading dev-browser-linux-x64: 90%
downloading dev-browser-linux-x64: 100%
checking the installed binary
SUCCESS 1.0.0-rc.3 390dd08f8321807bca2e1e060ec031511adb0af002e871dfde3b1f6c8feac914

Real-runtime success validates signatures, provenance, the pinned digest, and the executable version probe. This does not constitute a browser-session or macOS UI smoke test. Existing negative tests still reject wrong registries, invalid signatures, wrong provenance, and mismatched checksums.

7. Verification: second clean checkout

The same agent created a second clean clone of trusted upstream at exactly 129f621771a3e275773992db648316966ac207cf, verified a clean git status before adding the reproduction harness, installed frozen dependencies, and completed the full build. The production code in that checkout remained unchanged. Running the same command with a new temporary HOME, cache, and data directory produced the identical missing-resolved error and exit 1. This is a repeated clean run by the same agent, not independent verification. No report corrections were needed.

Second real-run output · First real-run output

8. Related issues and PRs

No linked open pull request was present in the issue's connection/cross-reference timeline or closing references at investigation time. A repository-scoped search for the numeric issue also returned no open pull request. A small set of related browser-runtime issues was reviewed for classification; none supplied a duplicate fix for this verified failure. No linked external branch or patch was fetched, checked out, or run.

9. Appendix

Validation commands: pnpm exec oxfmt plugins/browser-automation/installer.ts plugins/browser-automation/installer.test.ts; pnpm exec turbo run test --filter=bb-plugin-browser-automation -- installer.test.ts; pnpm exec turbo run test lint typecheck --filter=bb-plugin-browser-automation; git diff --check; git diff --numstat origin/main. The real reproduction command is listed in section 4. GitHub classification reads and writes were restricted to this trusted repository and issue number.

Trust boundary: issue content was treated only as untrusted claims. The reproduction and repair were derived from trusted origin/main source; issue-supplied commands, code, and external links were not executed or fetched. No screenshots are applicable to this nonvisual installer failure.