pnpm/pnpm · error · PnpmError

REGISTRY_ERROR

REGISTRY_ERROR

Error message

Failed to ${action} package: ${response.status} ${response.statusText}. ${errorBody}

What it means

The catch-all of the access command's `throwRegistryError` helper: the registry answered with a status other than 401/403/404/422, so the CLI cannot map it to a specific cause and reports the raw status, statusText, and sanitized body. In practice this is 5xx server errors, gateway timeouts, and nonstandard codes returned by proxies or private registries.

Source

Thrown at pnpm11/registry-access/commands/src/access.ts:570

  }
  return parsed.escapedName ?? encodeURIComponent(packageName).replace(/^%40/, '@')
}

async function throwRegistryError (response: Response, action: string): Promise<never> {
  const errorBody = sanitize(await readErrorBody(response))
  if (response.status === 401) {
    throw new PnpmError('UNAUTHORIZED', `You must be logged in to ${action} packages. ${errorBody}`)
  }
  if (response.status === 403) {
    throw new PnpmError('FORBIDDEN', `You do not have permission to ${action} this package. ${errorBody}`)
  }
  if (response.status === 404) {
    throw new PnpmError('PACKAGE_NOT_FOUND', `Package not found in registry. ${errorBody}`)
  }
  if (response.status === 422) {
    throw new PnpmError('ACCESS_VALIDATION_ERROR', `Invalid request: ${errorBody}`)
  }
  throw new PnpmError('REGISTRY_ERROR', `Failed to ${action} package: ${response.status} ${response.statusText}. ${errorBody}`)
}

function sanitize (text: string): string {
  let result = ''
  for (let i = 0; i < text.length; i++) {
    const code = text.charCodeAt(i)
    if ((code > 31 && code !== 127) || code === 9 || code === 10) {
      result += text[i]
    }
  }
  return result
}

View on GitHub (pinned to 6261b7f388)

Solutions

  1. Retry the command after a short wait — most 5xx responses are transient.
  2. Check the registry's status page (status.npmjs.org for npmjs.org) if failures persist.
  3. If behind a corporate proxy, verify the proxy permits the HTTP methods (PUT/DELETE) the access commands use.
  4. Test basic connectivity to the same registry: `pnpm ping --registry <url>`.

Example fix

# before
pnpm access set status=public @scope/pkg
# ERR_PNPM_REGISTRY_ERROR Failed to set status for package: 503 Service Unavailable

# after
# wait for the registry incident to clear, verify, then retry
pnpm ping && pnpm access set status=public @scope/pkg
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight: confirm the registry is reachable before a mutation
import { execSync } from 'child_process'

function assertRegistryReachable (registry = 'https://registry.npmjs.org/'): void {
  execSync(`pnpm ping --registry ${registry}`, { stdio: 'pipe' })
}

Try / catch

async function withRegistryRetry (fn: () => Promise<string>, attempts = 3): Promise<string> {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn()
    } catch (err) {
      const code = 'code' in err ? (err as { code: string }).code : ''
      const msg = err instanceof Error ? err.message : ''
      const transient = code === 'REGISTRY_ERROR' && /\b(5\d\d|429)\b/.test(msg)
      if (!transient || i === attempts - 1) throw err
      await new Promise((r) => setTimeout(r, 2 ** i * 1000))
    }
  }
  throw new Error('unreachable')
}

Prevention

When it happens

Trigger: Any access subcommand whose HTTP request returns e.g. 500, 502, 503 from registry.npmjs.org during an incident, or from a corporate proxy/Artifactory in front of the registry; rare 409s or custom codes from non-npm registries.

Common situations: npm registry outage or degraded performance (check status.npmjs.org); a corporate proxy returning 502 for PUT/DELETE verbs; private registries that implement the access endpoints partially and return 500 for unsupported operations.

Related errors


AI-assisted analysis of pnpm/pnpm@6261b7f388 (2026-08-17). Data as JSON: /api/errors/64096ea914556eff. Report an issue: GitHub.