{"record":{"id":"d4f40630c1ae8a7a","repo":"phacility/phabricator","slug":"failed-to-json-decode-rule-data","errorCode":null,"errorMessage":"Failed to JSON decode rule data!","messagePattern":"Failed to JSON decode rule data!","errorType":"validation","errorClass":"PhutilProxyException","httpStatus":500,"severity":"error","filePath":"src/applications/policy/controller/PhabricatorPolicyEditController.php","lineNumber":94,"sourceCode":"      $policy = head($policies);\n    } else {\n      $policy = id(new PhabricatorPolicy())\n        ->setRules(array($default_rule))\n        ->setDefaultAction(PhabricatorPolicy::ACTION_DENY);\n    }\n\n    $root_id = celerity_generate_unique_node_id();\n\n    $default_action = $policy->getDefaultAction();\n    $rule_data = $policy->getRules();\n\n    $errors = array();\n    if ($request->isFormPost()) {\n      $data = $request->getStr('rules');\n      try {\n        $data = phutil_json_decode($data);\n      } catch (PhutilJSONParserException $ex) {\n        throw new PhutilProxyException(\n          pht('Failed to JSON decode rule data!'),\n          $ex);\n      }\n\n      $rule_data = array();\n      foreach ($data as $rule) {\n        $action = idx($rule, 'action');\n        switch ($action) {\n          case 'allow':\n          case 'deny':\n            break;\n          default:\n            throw new Exception(pht(\"Invalid action '%s'!\", $action));\n        }\n\n        $rule_class = idx($rule, 'rule');\n        if (empty($rules[$rule_class])) {\n          throw new Exception(pht(\"Invalid rule class '%s'!\", $rule_class));","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/policy/controller/PhabricatorPolicyEditController.php#L76-L112","documentation":"Thrown by PhabricatorPolicyEditController while saving a custom policy from the policy edit dialog. The browser submits the whole rule list as a JSON string in the `rules` form field; when phutil_json_decode cannot parse it, the PhutilJSONParserException is rethrown wrapped in a PhutilProxyException with this message. It means the submitted payload is syntactically broken, before any rule semantics are examined.","triggerScenarios":"POSTing to the policy edit endpoint with a `rules` field that is not valid JSON: a hand-built curl payload, a request mangled by a proxy or browser extension, or stale celerity JS after an upgrade serializing the dialog incorrectly.","commonSituations":"Automating policy creation against the web endpoint instead of the UI, replaying and editing captured form posts, and half-upgraded installs serving a cached dialog bundle.","solutions":["Reproduce the save once in a normal browser session; if it works there, the failing client is sending malformed JSON.","When scripting the endpoint, build the rules array in code and send json_encode($rules) as the field so it is guaranteed valid JSON.","After an upgrade, clear the celerity cache and hard-refresh if the dialog itself submits garbage."],"exampleFix":"// before: hand-typed field, trailing comma breaks JSON\nrules=[{\"action\":\"allow\",}]\n// after: JSON array of rule objects, string-encoded\nrules=[{\"action\":\"allow\",\"rule\":\"PhabricatorProjectsPolicyRule\",\"value\":[\"PHID-PROJ-xxx\"]}]","handlingStrategy":"try-catch","validationCode":"// Build the field in code so it is always valid JSON.\n$rules = array(\n  array('action' => 'allow',\n        'rule' => 'PhabricatorProjectsPolicyRule',\n        'value' => array($project_phid)),\n);\n$post['rules'] = json_encode($rules);","typeGuard":null,"tryCatchPattern":"Wrap phutil_json_decode() in try { } catch (PhutilJSONParserException $ex) — the parser exception carries the failing byte offset. When rethrowing from a higher layer, wrap it as the controller does: throw new PhutilProxyException(pht('Failed to JSON decode rule data!'), $ex); so the low-level cause stays attached.","preventionTips":["Never hand-type the rules JSON; always json_encode a PHP array (or JSON.stringify in JS).","Test the payload once in the browser dialog before automating the endpoint.","Hard-refresh / clear celerity cache after upgrades so the dialog JS matches the server."],"tags":["phabricator","policy","json","form-post","custom-policy"],"backgroundTag":"json-parse-error","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}