phacility/phabricator · error · Exception

Capability "%s" does not exist.

Error message

Capability "%s" does not exist.

What it means

policy.lock is a JSON config option that force-locks object policies per capability. PolicyLockOptionType::validateOption() expands every PhabricatorPolicyCapability subclass into a key map and rejects any key in the JSON that is not a registered capability key. The whole config write is refused, keeping the lock set consistent with installed capability classes.

Source

Thrown at src/applications/policy/config/PolicyLockOptionType.php:16

<?php

final class PolicyLockOptionType
  extends PhabricatorConfigJSONOptionType {

  public function validateOption(PhabricatorConfigOption $option, $value) {
    $capabilities = id(new PhutilClassMapQuery())
      ->setAncestorClass('PhabricatorPolicyCapability')
      ->setUniqueMethod('getCapabilityKey')
      ->execute();

    $policy_phids = array();
    foreach ($value as $capability_key => $policy) {
      $capability = idx($capabilities, $capability_key);
      if (!$capability) {
        throw new Exception(
          pht(
            'Capability "%s" does not exist.',
            $capability_key));
      }
      if (phid_get_type($policy) !=
          PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {
        $policy_phids[$policy] = $policy;
      } else {
        try {
          $policy_object = PhabricatorPolicyQuery::getGlobalPolicy($policy);
        // this exception is not helpful here as its about global policy;
        // throw a better exception
        } catch (Exception $ex) {
          throw new Exception(
            pht(
              'Capability "%s" has invalid policy "%s".',
              $capability_key,
              $policy));

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Remove or correct the unknown capability key in the policy.lock configuration
  2. Enumerate valid keys by listing PhabricatorPolicyCapability subclasses (or the policy UI pickers) and use exactly those strings
  3. Re-validate saved policy.lock values after upgrades, since retired keys only surface on the next save

Example fix

// before: unknown capability key rejects the whole config
{"maniphest.priorities": "admin"}

// after: use a capability key that actually exists
{"diffusion.push": "admin"}
Defensive patterns

Strategy: validation

Validate before calling

// Validate policy.lock keys before saving.
$capabilities = id(new PhutilClassMapQuery())
  ->setAncestorClass('PhabricatorPolicyCapability')
  ->setUniqueMethod('getCapabilityKey')
  ->execute();
foreach ($value as $capability_key => $_) {
  if (empty($capabilities[$capability_key])) {
    throw new Exception(pht('Unknown capability key: %s', $capability_key));
  }
}

Type guard

function isKnownPolicyCapability($key) {
  static $keys;
  if ($keys === null) {
    $keys = id(new PhutilClassMapQuery())
      ->setAncestorClass('PhabricatorPolicyCapability')
      ->setUniqueMethod('getCapabilityKey')
      ->execute();
  }
  return isset($keys[$key]);
}

Prevention

When it happens

Trigger: Saving policy.lock containing a capability key no PhabricatorPolicyCapability subclass provides: a typo, a key removed by an upgrade, or a key defined by an extension that is no longer installed.

Common situations: Copy-pasting config between instances with different extensions installed; upgrading Phabricator where a capability was renamed or retired; hand-editing the policy.lock JSON.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/a95e48cb33986ba4. Report an issue: GitHub.