{"record":{"id":"3ec0b575cd45a3c3","repo":"phacility/phabricator","slug":"regular-expression-s-in-herald-rule-s-is-not","errorCode":null,"errorMessage":"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.","messagePattern":"Regular expression \"(.+?)\" in Herald rule \"(.+?)\" is not valid, or exceeded backtracking or recursion limits while executing\\. Verify the expression and correct it or rewrite it with less backtracking\\.","errorType":"validation","errorClass":"HeraldInvalidConditionException","httpStatus":null,"severity":"error","filePath":"src/applications/herald/adapter/HeraldAdapter.php","lineNumber":543,"sourceCode":"        // - /.*/S is evaluated same as /.*/SS.\n        $condition_pattern = $condition_value.'S';\n\n        foreach ((array)$field_value as $value) {\n          try {\n            $result = phutil_preg_match($condition_pattern, $value);\n          } catch (PhutilRegexException $ex) {\n            $message = array();\n            $message[] = pht(\n              'Regular expression \"%s\" in Herald rule \"%s\" is not valid, '.\n              'or exceeded backtracking or recursion limits while '.\n              'executing. Verify the expression and correct it or rewrite '.\n              'it with less backtracking.',\n              $condition_value,\n              $rule->getMonogram());\n            $message[] = $ex->getMessage();\n            $message = implode(\"\\n\\n\", $message);\n\n            throw new HeraldInvalidConditionException($message);\n          }\n\n          if ($result) {\n            return $result_if_match;\n          }\n        }\n        return !$result_if_match;\n      case self::CONDITION_REGEXP_PAIR:\n        // Match a JSON-encoded pair of regular expressions against a\n        // dictionary. The first regexp must match the dictionary key, and the\n        // second regexp must match the dictionary value. If any key/value pair\n        // in the dictionary matches both regexps, the condition is satisfied.\n        $regexp_pair = null;\n        try {\n          $regexp_pair = phutil_json_decode($condition_value);\n        } catch (PhutilJSONParserException $ex) {\n          throw new HeraldInvalidConditionException(\n            pht('Regular expression pair is not valid JSON!'));","sourceCodeStart":525,"sourceCodeEnd":561,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/herald/adapter/HeraldAdapter.php#L525-L561","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Test the exact pattern in PHP against representative (worst-case large) input: php -r 'var_dump(@preg_match(\"PATTERN\", $input), preg_last_error_msg());'.","Fix PCRE syntax: wrap the pattern in delimiters (e.g. /.../ or #...#) and use valid modifiers.","Rewrite to reduce backtracking: anchor the pattern, avoid nested quantifiers, prefer specific character classes, use possessive/atomic groups where supported.","Only as a last resort, raise pcre.backtrack_limit/pcre.recursion_limit in PHP configuration (can crash workers)."],"exampleFix":"// before (catastrophic backtracking on long titles)\n/^(\\d+-)+$\n\n// after (linear)\n/^[\\d-]+$/","handlingStrategy":"validation","validationCode":"// Validate a Herald regex before saving/using it, including backtracking:\n$sample = str_repeat('a', 100000); // worst-case sized input\n$result = @preg_match($pattern, $sample);\nif ($result === false || preg_last_error() !== PREG_NO_ERROR) {\n  // reject the pattern before it can break rule evaluation\n}","typeGuard":"function isValidHeraldRegexp($pattern) {\n  return @preg_match($pattern, '') !== false;\n}","tryCatchPattern":"try {\n  $adapter->doesConditionMatch($rule, $condition, $field_value);\n} catch (HeraldInvalidConditionException $ex) {\n  if (preg_match('/backtracking or recursion limits/', $ex->getMessage())) {\n    // rule regex is broken - fix the pattern; rule is skipped meanwhile\n  }\n}","preventionTips":["Always include delimiters around Herald regex conditions (/.../ or #...#).","Avoid nested quantifiers like (\\d+)+ or (.*)*; anchor patterns where possible.","Test patterns against the largest realistic field values (diffs, commit messages) before enabling the rule.","Watch rule transcripts for regex errors after enabling new text-matching rules."],"tags":["herald","regex","pcre","phabricator"],"backgroundTag":"invalid-regex-pattern","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}