composer/composer · error · UnexpectedValueException

Invalid custom dependency policy name "%s": this name is res

Error message

Invalid custom dependency policy name "%s": this name is reserved for a built-in dependency policy.

What it means

Thrown by PolicyConfig::assertCustomListNameAllowed() when a custom dependency-policy list name collides with a built-in reserved name (the RESERVED_NAMES set, currently 'advisories' and 'abandoned'). Repositories and users must not advertise a custom list under these names because Composer routes them to the dedicated built-in policy config.

Source

Thrown at src/Composer/Policy/PolicyConfig.php:139

    }

    /**
     * Reject custom-list names that collide with reserved or future-reserved
     * identifiers (RESERVED_NAMES, FUTURE_RESERVED_NAMES, or any
     * FUTURE_RESERVED_PREFIXES entry).
     *
     * In the normal fromConfig flow, built-in list keys (`advisories`, `malware`,
     * `abandoned`) and known non-list sibling keys (`ignore-unreachable`) are
     * filtered out before this check. The RESERVED_NAMES check is therefore
     * defence-in-depth for `advisories` and `abandoned` if that loop skip ever
     * changes; `malware` is intentionally absent from RESERVED_NAMES because
     * repositories are allowed to advertise a `malware` list, so it relies
     * solely on the loop's BUILTIN_LIST_NAMES skip.
     */
    private static function assertCustomListNameAllowed(string $listName): void
    {
        if (in_array($listName, self::RESERVED_NAMES, true)) {
            throw new \UnexpectedValueException(sprintf(
                'Invalid custom dependency policy name "%s": this name is reserved for a built-in dependency policy.',
                $listName
            ));
        }

        $error = self::getFutureReservedListNameError($listName);
        if ($error !== null) {
            throw new \UnexpectedValueException('Invalid custom dependency policy name: '.$error);
        }
    }

    /**
     * Reads config.policy with BC fallback to config.audit.
     */
    public static function fromConfig(Config $config): self
    {
        $policyRaw = $config->get('policy');
        $auditRaw = $config->get('audit');

View on GitHub (pinned to 6ffc117740)

Solutions

  1. Rename the custom list to a non-reserved identifier (e.g. 'internal-advisories', 'legacy-abandoned').
  2. Use the dedicated built-in config shape for advisories/abandoned/malware rather than a custom list.
  3. Cross-check proposed list names against PolicyConfig::RESERVED_NAMES and PolicyConfig::BUILTIN_LIST_NAMES before publishing.

Example fix

// before
['policy' => ['abandoned' => /* custom list config */]]

// after
['policy' => ['internal-abandoned' => /* custom list config */]]
Defensive patterns

Strategy: validation

Validate before calling

function listNameIsNotReserved(string $name): bool {
    return !in_array($name, \Composer\Policy\PolicyConfig::RESERVED_NAMES, true)
        && !in_array($name, \Composer\Policy\PolicyConfig::BUILTIN_LIST_NAMES, true);
}

Type guard

function isReservedListName(string $name): bool {
    return in_array($name, \Composer\Policy\PolicyConfig::RESERVED_NAMES, true);
}

Prevention

When it happens

Trigger: Defining `config.policy.advisories` or `config.policy.abandoned` as a custom list name, or a repository advertising a list named 'advisories'/'abandoned'. Reached at PolicyConfig.php:139 when in_array($listName, RESERVED_NAMES, true) is true.

Common situations: Repo publisher reuses a built-in name for a bespoke list; user pastes a config snippet that names a custom list 'abandoned'; tooling that synthesises list names from package metadata without filtering reserved words.

Related errors


AI-assisted analysis of composer/composer@6ffc117740 (2026-08-07). Data as JSON: /api/errors/eea53cbb7dcdc889. Report an issue: GitHub.