Skip to content

fix(artifacts): resolve the snapshot existence check against JFrog - #178

Open
senthanal wants to merge 1 commit into
release/26.1from
fix/pr-snapshot-package-exists-registry
Open

fix(artifacts): resolve the snapshot existence check against JFrog#178
senthanal wants to merge 1 commit into
release/26.1from
fix/pr-snapshot-package-exists-registry

Conversation

@senthanal

Copy link
Copy Markdown
Contributor

Problem

publish-pr-snapshot fails with 403 Forbidden on every push to a PR after the first one:

npm notice version: 0.0.0-cplace-migration-26.4-20260731-114649-46
npm notice Publishing to *** with tag latest and restricted access
npm error code E403
npm error 403 403 Forbidden - PUT ***@cplace-loomeo-portfolio-management-fe%2floomeo-portfolio

The version being published already exists on JFrog, and Artifactory rejects redeploy.

Root cause

Utils.getPRVersion() builds the suffix from branch name + PR number only — no timestamp — so the version is constant for the lifetime of a PR, unlike getUniqueSnapshotIdentifier(). NxProject.deleteArtifact() is what makes that workable: it unpublishes the version before republishing it. Its guard never let that happen.

packageExists() ran:

const scopeSearchResult = execSync(`npm show ${pkg} --json`).toString();

No cwd, no --registry. Every sibling npm call in the file passes the dist directory (npm unpublish in the same method, Utils.getAllSnapshotVersionsOfPackage); this one didn't, so it executed at the repository root and resolved against the public registry:

Checking if package exists in registry
npm error 404 Not Found - GET https://registry.npmjs.org/@cplace-loomeo-portfolio-management-fe%2floomeo-portfolio
Package …@0.0.0-cplace-migration-26.4-20260731-114649-46 does not exist in the registry. Skipping deletion.

404 → catchreturn false → unpublish skipped → publish collides → 403.

The same two log lines appear in the job that succeeded, on the PR's first push. The check has always resolved against the wrong registry; it was harmless only while the version genuinely did not exist yet.

A second defect kept it invisible: the catch reported every failure as "not found". An auth failure or a registry outage produces the identical silent skip and the identical 403 one step later.

Fix

  • packageExists() pins --registry to getJfrogUrl() — the same registry npm publish and npm unpublish already target — and runs in the dist directory, whose .npmrc carries the credentials for it.
  • The dist bootstrap in deleteArtifact() moved above the existence check, so the ONLY_DELETE_ARTIFACTS path is authenticated too. Previously that path wrote the .npmrc after the check that needed it.
  • Only a genuine 404 now counts as absent. Any other failure throws with the underlying stderr rather than guessing "absent" and letting the publish collide.

Impact

Every product repo that pushes a second snapshot to the same PR hits this. Observed on cplace-dataciders-portfolio-management-fe PR #46: snapshot job green on 2026-08-06, then 403 on 08-13, 08-16, 08-17 and 08-18 — once per push, deterministic.

Note that already-stuck PRs need their existing snapshot version deleted once by hand; this fix stops the next one from getting stuck, it doesn't retroactively clear one.

Tests

Five tests added around deleteArtifact — there were none before:

  • the lookup targets the JFrog registry and runs in dist (the regression itself)
  • version present → npm unpublish runs
  • version absent from a known package → deletion skipped
  • package unknown to the registry (E404) → deletion skipped
  • lookup broke for any other reason → fails, does not skip silently

npm test: 51 passed, up from 46, with the failure count unchanged at 8 — those 8 fail identically on unmodified release/26.1 and are unrelated (2 in nx-project.test.ts around getMarkdownLink and e2e isPublishable, 6 in upmerge). npx tsc --noEmit and prettier --check both clean.

🤖 Generated with Claude Code

PR snapshots are versioned `0.0.0-<branch>-<prNumber>`, which is constant
for the lifetime of a PR, so every push after the first republishes an
identical version. NxProject.deleteArtifact compensates by unpublishing
the version before publishing it again, but its guard never let that
happen.

packageExists ran `npm show <pkg> --json` with no cwd and no --registry.
From the repository root that resolves against the public registry, where
the private scope does not exist, so the lookup 404'd, the guard read the
404 as "version absent", the unpublish was skipped, and npm publish then
PUT a version that was already on JFrog — which Artifactory rejects with
403 Forbidden.

The check now pins --registry to the JFrog URL the publish and unpublish
already target, and runs in the dist directory whose .npmrc carries the
credentials for it. The dist bootstrap in deleteArtifact moved above the
check so the ONLY_DELETE_ARTIFACTS path is authenticated too.

A second defect kept this invisible: the catch reported every failure as
"not found", so an auth error or a registry outage produced the same
silent skip and the same 403 one step later. Only a 404 now counts as
absent; anything else fails the job with the underlying cause.

Observed on cplace-dataciders-portfolio-management-fe PR #46, where the
snapshot job succeeded once on 2026-08-06 and then failed identically on
every subsequent push.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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