phacility/phabricator · error · Exception

Invalid rule class '%s'!

Error message

Invalid rule class '%s'!

What it means

The `rule` field of each custom-policy rule must name a class present in the editor's rule map. That map is built with PhutilClassMapQuery over installed PhabricatorPolicyRule subclasses, then filtered by canApplyToObject($object), so a class is rejected both when it is not installed at all and when it cannot apply to the object being edited. An unknown class means no rule object could ever be built, so the save aborts.

Source

Thrown at src/applications/policy/controller/PhabricatorPolicyEditController.php:112

        throw new PhutilProxyException(
          pht('Failed to JSON decode rule data!'),
          $ex);
      }

      $rule_data = array();
      foreach ($data as $rule) {
        $action = idx($rule, 'action');
        switch ($action) {
          case 'allow':
          case 'deny':
            break;
          default:
            throw new Exception(pht("Invalid action '%s'!", $action));
        }

        $rule_class = idx($rule, 'rule');
        if (empty($rules[$rule_class])) {
          throw new Exception(pht("Invalid rule class '%s'!", $rule_class));
        }

        $rule_obj = $rules[$rule_class];

        $value = $rule_obj->getValueForStorage(idx($rule, 'value'));

        $rule_data[] = array(
          'action' => $action,
          'rule' => $rule_class,
          'value' => $value,
        );
      }

      // Filter out nonsense rules, like a "users" rule without any users
      // actually specified.
      $valid_rules = array();
      foreach ($rule_data as $rule) {
        $rule_class = $rule['rule'];

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Open the policy edit dialog for that object and use exactly the rule names it offers; copy the class name verbatim.
  2. If the rule came from a disabled application or removed extension, re-enable it and retry.
  3. Programmatically, rebuild the map the same way the controller does (PhutilClassMapQuery on PhabricatorPolicyRule, filtered by canApplyToObject) and validate payloads against it.

Example fix

// before: guessed class name
{"action":"allow","rule":"ProjectPolicyRule","value":[]}
// after: real installed rule class, as offered by the edit dialog
{"action":"allow","rule":"PhabricatorProjectsPolicyRule","value":["PHID-PROJ-xxx"]}
Defensive patterns

Strategy: validation

Validate before calling

// Rebuild the same map the controller uses and screen the payload.
$rules = id(new PhutilClassMapQuery())
  ->setAncestorClass('PhabricatorPolicyRule')
  ->execute();
foreach ($rules as $key => $rule) {
  if (!$rule->canApplyToObject($object)) {
    unset($rules[$key]);
  }
}
$valid = array_keys($rules);
foreach ($payload as $rule) {
  if (!in_array(idx($rule, 'rule'), $valid, true)) {
    // drop or fix this rule before POSTing
  }
}

Prevention

When it happens

Trigger: Posting a rule whose `rule` value is a typo'd class name; referencing a valid rule from a disabled application or a removed custom extension; or referencing a rule class that exists but does not apply to the object type being edited (filtered out by canApplyToObject).

Common situations: Disabling an application whose policy rules were previously used in custom rules, removing a custom PhabricatorPolicyRule extension during cleanup, and hand-built payloads that guess class names.

Related errors


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