phacility/phabricator · error · HeraldInvalidConditionException

Regular expression "%s" in Herald rule "%s" is not valid, or

Error message

Regular expression "%s" in Herald rule "%s" is not valid, or exceeded backtracking or recursion limits while executing. Verify the expression and correct it or rewrite it with less backtracking.

What it means

For a Herald regex condition, the adapter runs preg_match() through PhutilRegexException-guarded code; if the pattern is not valid PCRE or PCRE hits its backtrack_limit/recursion_limit while matching (PhutilRegexException is thrown), the adapter wraps it in HeraldInvalidConditionException together with the pattern, the rule monogram, and the underlying message. The rule is then reported as errored in the transcript and does not apply.

Source

Thrown at src/applications/herald/adapter/HeraldAdapter.php:543

        // - /.*/S is evaluated same as /.*/SS.
        $condition_pattern = $condition_value.'S';

        foreach ((array)$field_value as $value) {
          try {
            $result = phutil_preg_match($condition_pattern, $value);
          } catch (PhutilRegexException $ex) {
            $message = array();
            $message[] = pht(
              'Regular expression "%s" in Herald rule "%s" is not valid, '.
              'or exceeded backtracking or recursion limits while '.
              'executing. Verify the expression and correct it or rewrite '.
              'it with less backtracking.',
              $condition_value,
              $rule->getMonogram());
            $message[] = $ex->getMessage();
            $message = implode("\n\n", $message);

            throw new HeraldInvalidConditionException($message);
          }

          if ($result) {
            return $result_if_match;
          }
        }
        return !$result_if_match;
      case self::CONDITION_REGEXP_PAIR:
        // Match a JSON-encoded pair of regular expressions against a
        // dictionary. The first regexp must match the dictionary key, and the
        // second regexp must match the dictionary value. If any key/value pair
        // in the dictionary matches both regexps, the condition is satisfied.
        $regexp_pair = null;
        try {
          $regexp_pair = phutil_json_decode($condition_value);
        } catch (PhutilJSONParserException $ex) {
          throw new HeraldInvalidConditionException(
            pht('Regular expression pair is not valid JSON!'));

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Test the exact pattern in PHP against representative (worst-case large) input: php -r 'var_dump(@preg_match("PATTERN", $input), preg_last_error_msg());'.
  2. Fix PCRE syntax: wrap the pattern in delimiters (e.g. /.../ or #...#) and use valid modifiers.
  3. Rewrite to reduce backtracking: anchor the pattern, avoid nested quantifiers, prefer specific character classes, use possessive/atomic groups where supported.
  4. Only as a last resort, raise pcre.backtrack_limit/pcre.recursion_limit in PHP configuration (can crash workers).

Example fix

// before (catastrophic backtracking on long titles)
/^(\d+-)+$

// after (linear)
/^[\d-]+$/
Defensive patterns

Strategy: validation

Validate before calling

// Validate a Herald regex before saving/using it, including backtracking:
$sample = str_repeat('a', 100000); // worst-case sized input
$result = @preg_match($pattern, $sample);
if ($result === false || preg_last_error() !== PREG_NO_ERROR) {
  // reject the pattern before it can break rule evaluation
}

Type guard

function isValidHeraldRegexp($pattern) {
  return @preg_match($pattern, '') !== false;
}

Try / catch

try {
  $adapter->doesConditionMatch($rule, $condition, $field_value);
} catch (HeraldInvalidConditionException $ex) {
  if (preg_match('/backtracking or recursion limits/', $ex->getMessage())) {
    // rule regex is broken - fix the pattern; rule is skipped meanwhile
  }
}

Prevention

When it happens

Trigger: A rule uses a regexp condition whose pattern is invalid (missing delimiters, bad syntax) or that catastrophically backtracks when run against the actual field value — large diffs, long commit messages, or big text bodies in differential revisions.

Common situations: Pattern written without delimiters ('\d+' instead of '/\d+/'); nested quantifiers like (\d+)+ or (.*)* against long input; patterns that tested fine on short samples but explode in production; PCRE stack/backtrack limits hit on very large fields.

Related errors


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