Refuse to publish a package npm cannot install

4bb3f52256ea · AtlantisPleb · · parent 3909cb48b902

Refuse to publish a package npm cannot install

This workspace writes dependencies as pnpm catalog references, and only
pnpm rewrites them to concrete versions when packing. A package packed with
npm pack publishes a manifest npm itself cannot resolve, and the consumer
sees EUNSUPPORTEDPROTOCOL "catalog:" — a message naming the protocol
rather than the mistake. It has reached the registry that way before.

The verifier already packed the right way and installed what it packed, so
it would have caught this. Nothing made publishing run it. It now runs as
prepublishOnly, and it reads the packed manifest and refuses a catalog: or
workspace: specifier by name, so the failure says what to do instead of
surfacing deep in an install log.

Proven both ways: the verifier passes on the pnpm path, and packing with
npm pack instead makes it fail naming dependencies.effect: catalog:.

AGENTS.md records the rule, that npm publish <file.tgz> skips the
prepublishOnly lifecycle, and that registry metadata caches locally — so a
publish that looks like it failed on npm view may have succeeded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnhfrafYx5ZGaMbzZEJQ2d
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified AGENTS.md
  • modified packages/openagents-cli/package.json
  • modified packages/openagents-cli/scripts/verify-packed-install.mjs

Diff

3 files changed, +53 -1

AGENTS.md modified +22

@@ -806,6 +806,28 @@ dies with its Codex thread. With the flag unset there is zero behavior change.

806 806
807 807
## Deploying & Releasing
808 808
809
- **Publish npm packages with pnpm, never npm.** This workspace uses pnpm
810
  catalogs, so a dependency is written `catalog:` in the source manifest and
811
  must be rewritten to a concrete version before it is published. `pnpm pack`
812
  and `pnpm publish` do that rewrite; `npm pack` does not. A package packed
813
  with `npm pack` publishes a manifest npm itself cannot resolve, and the
814
  consumer sees `EUNSUPPORTEDPROTOCOL "catalog:"` — a message that names the
815
  protocol rather than the mistake. It has shipped that way before.
816
- **Never publish a tarball you have not verified installs.** For the CLI,
817
  `pnpm --filter @openagentsinc/cli run verify:package` packs the way a publish
818
  packs, refuses a manifest carrying `catalog:` or `workspace:` specifiers,
819
  installs the tarball into an empty project with `npm`, and runs the packed
820
  binary. It runs automatically as `prepublishOnly`. Note that
821
  `npm publish <file.tgz>` skips that lifecycle, so if you publish a pre-packed
822
  tarball you must run the verifier yourself first.
823
- **Confirm the published artifact from the registry, not from the build.**
824
  Install the exact published version into an empty directory and run a real
825
  command against a live surface. Registry metadata is cached locally, so
826
  `npm view` and `npm install` may report the previous version for a few
827
  minutes after a successful publish; use `--prefer-online`, or read
828
  `https://registry.npmjs.org/<package>` directly, before concluding a publish
829
  failed.
830
809 831
- **`docs/DEPLOYMENT.md` is the single hub for every deploy / publish / release.**
810 832
  Read it first for any of: deploying the `openagents.com` Cloud Run service,
811 833
  publishing Pylon to npm, publishing signed Pylon binaries, the
packages/openagents-cli/package.json modified +2 -1

@@ -50,7 +50,8 @@

50 50
    "test": "vp test --run",
51 51
    "typecheck": "tsc -p tsconfig.json --noEmit",
52 52
    "verify": "pnpm run fmt:check && pnpm run lint && pnpm run typecheck && pnpm run test && pnpm run build",
53
    "verify:package": "node scripts/verify-packed-install.mjs"
53
    "verify:package": "node scripts/verify-packed-install.mjs",
54
    "prepublishOnly": "pnpm run verify:package"
54 55
  },
55 56
  "dependencies": {
56 57
    "@effect/platform-node": "catalog:",
packages/openagents-cli/scripts/verify-packed-install.mjs modified +29

@@ -48,6 +48,35 @@ try {

48 48
  }
49 49
50 50
  const tarball = join(tarballDirectory, tarballs[0]);
51
52
  // A workspace specifier that survives packing makes the published package
53
  // uninstallable: `npm install` cannot resolve `catalog:` or `workspace:` and
54
  // fails with EUNSUPPORTEDPROTOCOL, which names the protocol and not the
55
  // mistake. `pnpm pack` rewrites them and `npm pack` does not, so this is
56
  // exactly what a publish packed the wrong way looks like. Read from the
57
  // packed manifest rather than the source one, because the rewrite is the
58
  // thing under test.
59
  const packedManifest = JSON.parse(
60
    run("tar", ["-xOf", tarball, "package/package.json"]).stdout,
61
  );
62
63
  const unresolved = ["dependencies", "peerDependencies", "optionalDependencies"].flatMap(
64
    (field) =>
65
      Object.entries(packedManifest[field] ?? {})
66
        .filter(([, range]) => /^(catalog:|workspace:)/.test(String(range)))
67
        .map(([name, range]) => `${field}.${name}: ${String(range)}`),
68
  );
69
70
  if (unresolved.length > 0) {
71
    throw new Error(
72
      "The packed manifest carries workspace specifiers npm cannot resolve, so " +
73
        "the published package would not install:\n  " +
74
        unresolved.join("\n  ") +
75
        "\nPack and publish with pnpm, never npm: `pnpm publish` rewrites these " +
76
        "to concrete versions and `npm pack` leaves them.",
77
    );
78
  }
79
51 80
  run("npm", ["install", tarball, "--no-audit", "--no-fund", "--no-package-lock", "--save-exact"], {
52 81
    cwd: consumerDirectory,
53 82
  });

This page updates live while a promote is in flight · changelog