fix: hand npm the release artifact's absolute path, so publishing finds it #83

Merged
Zayin_Admin merged 1 commits from fix/publish-absolute-artifact-path into main 2026-10-08 23:22:00 +00:00
Member

Fixes the first real 0.2.0 Publish packages run (run 219, job publish), which failed on its first package before anything was published:

Package train 0.2.0 (debb8df…) validated: @ekklesia/contracts, …
Publishing ekklesia-contracts-0.2.0.tgz failed: npm error code ENOENT

Root cause

The workflow passes a relative release path: --release "$(find package-train -mindepth 1 -maxdepth 1 -type d)", which expands to package-train/ekklesia-packages-0.2.0.

  • Validation passed. The tool validated the artifacts relative to the job's working directory, where they exist.
  • npm then looked in the wrong place. npm runs in the tool's private temporary home, and was given the same relative path:
Value
npm's cwd /tmp/ekklesia-publish-XXXXXX
argument package-train/ekklesia-packages-0.2.0/ekklesia-contracts-0.2.0.tgz
resolved to /tmp/ekklesia-publish-XXXXXX/package-train/…, which doesn't exist: ENOENT
the file is really at <workspace>/package-train/ekklesia-packages-0.2.0/ekklesia-contracts-0.2.0.tgz
  • Why tests missed it. The disposable-registry suite always made the release path absolute first, so only the real workflow passed a relative one.
  • Reproduced locally. The same failure appears from the same commit, with the Actions layout, the workflow's exact command, and an npm shim recording argv and cwd.

Fix

  • Absolute paths from validation onwards. loadReleaseTrain resolves the release directory once.
  • Guard at the npm boundary. npm is only ever given the artifact's absolute path; anything else is refused.
  • Re-check the file before upload. Just before each upload, the file is checked against the validated bytes again. It's the same file, not copied, repacked or rebuilt.
  • Diagnostics:
    • the tool logs @ekklesia/<name>@<version>: publishing <absolute path> (sha256 …);
    • a failure names that path and includes npm's code/path/syscall lines;
    • npm's temporary home is masked, and the token is never shown.

After the fix, the reproduction shows npm receiving /…/package-train/ekklesia-packages-0.2.0/ekklesia-contracts-0.2.0.tgz. Its SHA-256 (349e2cf2…) equals SHA256SUMS, and npm reads it and gets as far as the upload.

Tests

  • New unit tests use a recording fake npm. All three fail on the previous tool and pass now:
    • a release nested under package-train/ and named by a relative path from another directory loads with absolute artifact paths;
    • npm's argv is exactly publish <absolute validated path>, the file exists, npm runs in its own directory, and the error names the path without the token;
    • an artifact changed after validation is refused before npm runs.
  • New disposable-Gitea test: the CLI runs exactly as the workflow does (cwd holding package-train/<release>, relative --release, --manifest-sha256, --record, --require-public-read). All nine packages publish and round-trip byte for byte. The suite passes 11 of 11, and the partial-train, burned-version and tag-retry scenarios are unchanged.
  • Full gates:
    • pnpm verify passes (2,588 tests);
    • pnpm test:package-release passes strict, and the 0.2.0 tarballs are byte-identical to the release's (contracts 349e2cf2…, module-pack 2da58cf7…).

Real registry untouched

  • An anonymous search returns {"objects":[],"total":0}, and every @ekklesia/* lookup is 404.
  • No tags beyond v0.1.2.
  • Nothing was published, and the Publish packages workflow wasn't run.

🤖 Generated with Claude Code

Fixes the first real 0.2.0 **Publish packages** run (run 219, job `publish`), which failed on its first package before anything was published: ``` Package train 0.2.0 (debb8df…) validated: @ekklesia/contracts, … Publishing ekklesia-contracts-0.2.0.tgz failed: npm error code ENOENT ``` ## Root cause The workflow passes a **relative** release path: `--release "$(find package-train -mindepth 1 -maxdepth 1 -type d)"`, which expands to `package-train/ekklesia-packages-0.2.0`. - **Validation passed.** The tool validated the artifacts relative to the job's working directory, where they exist. - **npm then looked in the wrong place.** npm runs in the tool's private temporary home, and was given the same relative path: | | Value | |---|---| | npm's cwd | `/tmp/ekklesia-publish-XXXXXX` | | argument | `package-train/ekklesia-packages-0.2.0/ekklesia-contracts-0.2.0.tgz` | | resolved to | `/tmp/ekklesia-publish-XXXXXX/package-train/…`, which doesn't exist: **ENOENT** | | the file is really at | `<workspace>/package-train/ekklesia-packages-0.2.0/ekklesia-contracts-0.2.0.tgz` | - **Why tests missed it.** The disposable-registry suite always made the release path absolute first, so only the real workflow passed a relative one. - **Reproduced locally.** The same failure appears from the same commit, with the Actions layout, the workflow's exact command, and an npm shim recording argv and cwd. ## Fix - **Absolute paths from validation onwards.** `loadReleaseTrain` resolves the release directory once. - **Guard at the npm boundary.** npm is only ever given the artifact's **absolute** path; anything else is refused. - **Re-check the file before upload.** Just before each upload, the file is checked against the validated bytes again. It's the same file, not copied, repacked or rebuilt. - **Diagnostics:** - the tool logs `@ekklesia/<name>@<version>: publishing <absolute path> (sha256 …)`; - a failure names that path and includes npm's `code`/`path`/`syscall` lines; - npm's temporary home is masked, and the token is never shown. After the fix, the reproduction shows npm receiving `/…/package-train/ekklesia-packages-0.2.0/ekklesia-contracts-0.2.0.tgz`. Its SHA-256 (`349e2cf2…`) equals `SHA256SUMS`, and npm reads it and gets as far as the upload. ## Tests - **New unit tests** use a recording fake npm. All three fail on the previous tool and pass now: - a release nested under `package-train/` and named by a relative path from another directory loads with absolute artifact paths; - npm's argv is exactly `publish <absolute validated path>`, the file exists, npm runs in its own directory, and the error names the path without the token; - an artifact changed after validation is refused before npm runs. - **New disposable-Gitea test:** the CLI runs exactly as the workflow does (cwd holding `package-train/<release>`, relative `--release`, `--manifest-sha256`, `--record`, `--require-public-read`). All nine packages publish and round-trip byte for byte. The suite passes 11 of 11, and the partial-train, burned-version and tag-retry scenarios are unchanged. - **Full gates:** - `pnpm verify` passes (2,588 tests); - `pnpm test:package-release` passes strict, and the 0.2.0 tarballs are byte-identical to the release's (`contracts` `349e2cf2…`, `module-pack` `2da58cf7…`). ## Real registry untouched - An anonymous search returns `{"objects":[],"total":0}`, and every `@ekklesia/*` lookup is 404. - No tags beyond `v0.1.2`. - Nothing was published, and the Publish packages workflow wasn't run. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ekklesia-bot added 1 commit 2026-10-08 22:52:14 +00:00
fix: hand npm the release artifact's absolute path, so publishing finds it
CI / verify (pull_request) Successful in 1m5s
CI / infrastructure-smoke (pull_request) Successful in 37s
CI / migration-smoke (pull_request) Successful in 1m52s
CI / package-release (pull_request) Successful in 34s
CI / image-build (pull_request) Successful in 24s
CI / deploy-smoke (same-origin) (pull_request) Successful in 50s
CI / deploy-smoke (split) (pull_request) Successful in 1m30s
CI / package-publication (pull_request) Successful in 49s
1caffc5f26
The first real 0.2.0 publish failed on its first package with ENOENT:
"Publishing ekklesia-contracts-0.2.0.tgz failed: npm error code ENOENT".

The publish workflow passes a relative release path,
--release "$(find package-train …)", which is
package-train/ekklesia-packages-0.2.0. The tool validated the artifacts
relative to the job's working directory, but ran npm in its own
temporary home (cwd /tmp/ekklesia-publish-XXXXXX) with the same relative
path, package-train/ekklesia-packages-0.2.0/ekklesia-contracts-0.2.0.tgz,
which there names nothing. The disposable-registry suite always passed
an absolute path, so it never saw this.

- loadReleaseTrain resolves the release directory once; every artifact
  path is absolute from validation on, and npm is given only that exact
  file's absolute path (anything else is refused at the npm boundary).
- Just before each upload the file is checked against the validated
  bytes once more; the tool logs the absolute path it publishes, and a
  failure names it with npm's path/syscall lines (npm's temporary home
  masked, the token never shown).
- Regression tests: unit tests with a recording npm (relative nested
  release from another cwd; npm's argv, cwd, and the file's existence; a
  changed artifact refused before npm runs), which fail on the previous
  tool; and a disposable-Gitea test that runs the CLI exactly as the
  workflow does, from the Actions artifact layout, round-tripping all
  nine packages byte for byte.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Zayin_Admin merged commit 1788d17f0a into main 2026-10-08 23:22:00 +00:00
Zayin_Admin deleted branch fix/publish-absolute-artifact-path 2026-10-08 23:22:00 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Zayin/Ekklesia#83