## TL;DR
A 404 on a private package almost never means the package is missing; it means the request went to the wrong registry or went unauthenticated. Point the scope at the right registry and supply a valid token in CI, then confirm with a metadata fetch before reinstalling.

```text
npm ERR! 404 Not Found - GET https://YOUR-REGISTRY/[SCOPE]%2f[PACKAGE] - Not found
```

## Steps

1. Check which registry the scope is configured to use:
```bash
npm config get @[SCOPE]:registry
npm config get registry
```
Expected: the scope-specific line returns your private registry URL. If it is empty, npm falls back to the public registry, which 404s on every private package.

2. Confirm the CI job actually has an auth token configured:
```bash
npm config get //[REGISTRY_HOST]/:_authToken
```
Expected: a token value is returned. If empty, npm sends the request anonymously and the registry hides private packages as 404.

3. Fetch the package metadata directly to separate auth problems from naming problems:
```bash
npm view @[SCOPE]/[PACKAGE] version --registry=https://YOUR-REGISTRY_HOST/
```
Expected: a version string. A 404 here with correct auth means the name or scope in package.json does not match what is published.

4. In CI, set the registry and token explicitly in the job before install:
```bash
npm config set @[SCOPE]:registry https://YOUR-REGISTRY_HOST/
npm config set //[REGISTRY_HOST]/:_authToken "[TOKEN VALUE]"
npm ci
```
Expected: `npm ci` resolves the private package from the private registry and installs cleanly.

5. If the 404 persists with working auth, check the lockfile for drift:
```bash
grep -A2 "@[SCOPE]/[PACKAGE]" package-lock.json | head -20
```
Expected: the resolved URL matches the registry from step 1. A lockfile resolved against a different registry reintroduces the 404 on every `npm ci`.

## When to use
- `npm ci` or `npm install` 404s on a scoped private package in CI
- The same install works on a developer laptop but not in the pipeline
- A newly published private package 404s immediately after publish

## When not to use
- The 404 is on a public package, which is usually a typo or an unpublished version pin
- The failure is a 401 or 403, which is an explicit auth rejection rather than a routing problem
- Installs fail on all packages with network errors, which is a runner egress issue

## Why it happens
npm resolves scoped packages using the scope-to-registry mapping, and private registries return 404 (not 401) for unauthenticated requests to avoid leaking package existence. So a missing `npmrc` entry, an expired CI token, or a lockfile pointing at the public registry all surface as the same misleading 404.

## Edge cases
- Tokens scoped to a single package or with read-only CI scope 404 on packages outside their grant
- A trailing slash mismatch between the configured registry URL and the auth token path silently drops auth; keep both forms identical
- npm caches the 404 aggressively; after fixing config, rerun with the cache cleared for that package or the stale 404 returns
- Monorepo workspaces hoist installs, so the failing scope mapping may live in a workspace root `.npmrc`, not the package directory

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_Psos_FUb-jLzfUWhkobIhg
