pnpm/pnpm · error · PnpmError

ACCESS_REVOKE_INVALID_TEAM

ACCESS_REVOKE_INVALID_TEAM

Error message

Invalid team "${scopeTeam}". Format must be "scope:team".

What it means

Thrown by `pnpm access revoke` when the first argument does not contain a colon. Like the grant path, revokeAccess splits the team specifier on `:` to build the `/-team/<scope>/<team>/package` URL for the DELETE request, so a value without the `scope:team` shape is rejected client-side. The check is purely syntactic — it does not confirm the team exists on the registry.

Source

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

  if (!response.ok) {
    await throwRegistryError(response, `grant ${permissions} access for ${scopeTeam} on`)
  }

  return `+${scopeTeam} (${permissions}): ${packageName}`
}

async function revokeAccess (
  opts: AccessOptions,
  params: string[]
): Promise<string> {
  if (params.length < 1) {
    throw new PnpmError('ACCESS_REVOKE_ARGS_REQUIRED', 'scope:team and package name are required (e.g., pnpm access revoke @scope:developers @scope/pkg)')
  }

  const scopeTeam = params[0]
  if (!scopeTeam.includes(':')) {
    throw new PnpmError('ACCESS_REVOKE_INVALID_TEAM', `Invalid team "${scopeTeam}". Format must be "scope:team".`)
  }

  const packageName = params[1]
  if (!packageName) {
    throw new PnpmError('ACCESS_REVOKE_PACKAGE_REQUIRED', 'Package name is required (e.g., pnpm access revoke @scope:developers @scope/pkg)')
  }

  const [scope, team] = scopeTeam.split(':')
  const registriesByScope = getRegistries(opts)
  const registryUrl = pickRegistryForPackage(registriesByScope, packageName)
  const authHeader = getAuthHeaderForRegistry(opts.configByUri, registryUrl, packageName)
  const fetchFromRegistry = createFetchFromRegistry(opts)
  const otp = opts.cliOptions?.otp

  const revokeUrl = new URL(`-/team/${encodeURIComponent(scope.startsWith('@') ? scope.slice(1) : scope)}/${encodeURIComponent(team)}/package`, normalizeRegistryUrl(registryUrl)).href
  const response = await fetchFromRegistry(revokeUrl, {
    authHeaderValue: authHeader,
    method: 'DELETE',

View on GitHub (pinned to 6261b7f388)

Solutions

  1. Use the full `scope:team` form: `pnpm access revoke @myscope:developers @scope/pkg`.
  2. List existing teams with `pnpm team ls @myscope` to get the exact team name.
  3. Verify the separator is an ASCII colon.

Example fix

# before
pnpm access revoke developers @scope/pkg

# after
pnpm access revoke @myscope:developers @scope/pkg
Defensive patterns

Strategy: validation

Validate before calling

if (!scopeTeam.includes(':')) {
  throw new Error(`team must be in "scope:team" format, got "${scopeTeam}"`)
}
await handler(opts, ['revoke', scopeTeam, packageName])

Type guard

function isScopeTeamSpec (value: string): value is `${string}:${string}` {
  const parts = value.split(':')
  return parts.length === 2 && parts[0].length > 0 && parts[1].length > 0
}

Try / catch

try {
  await handler(opts, ['revoke', scopeTeam, packageName])
} catch (err) {
  if ('code' in err && (err as { code: string }).code === 'ACCESS_REVOKE_INVALID_TEAM') {
    // fix the team format (prepend the scope) and retry
  } else {
    throw err
  }
}

Prevention

When it happens

Trigger: `pnpm access revoke developers @scope/pkg` or `pnpm access revoke @myscope @scope/pkg` — any params[0] failing `scopeTeam.includes(':')`.

Common situations: Omitting the scope prefix because it seems implied by the package; pasting a team name copied from a notification that dropped the scope; typo such as replacing the colon with a dash or space.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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