{"record":{"id":"da48cd86ca238dd7","repo":"phacility/phabricator","slug":"commit-message-field-s-was-expected-to-render-a","errorCode":null,"errorMessage":"Commit message field \"%s\" was expected to render a string or null value, but rendered a \"%s\" instead.","messagePattern":"Commit message field \"(.+?)\" was expected to render a string or null value, but rendered a \"(.+?)\" instead\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/differential/conduit/DifferentialGetCommitMessageConduitAPIMethod.php","lineNumber":108,"sourceCode":"      // from, so we hit the same validation logic for values which came over\n      // the wire and which we generated.\n      $field_value = $field->readFieldValueFromConduit($field_value);\n\n      $value_map[$field_key] = $field_value;\n    }\n\n    $key_title = DifferentialTitleCommitMessageField::FIELDKEY;\n\n    $commit_message = array();\n    foreach ($field_list as $field_key => $field) {\n      $label = $field->getFieldName();\n      $wire_value = $value_map[$field_key];\n      $value = $field->renderFieldValue($wire_value);\n\n      $is_template = ($is_create && $field->isTemplateField());\n\n      if (!is_string($value) && !is_null($value)) {\n        throw new Exception(\n          pht(\n            'Commit message field \"%s\" was expected to render a string or '.\n            'null value, but rendered a \"%s\" instead.',\n            $field->getFieldKey(),\n            gettype($value)));\n      }\n\n      $is_title = ($field_key == $key_title);\n\n      if ($value === null || !strlen($value)) {\n        if ($is_template) {\n          $commit_message[] = $label.': ';\n        }\n      } else {\n        if ($is_title) {\n          $commit_message[] = $value;\n        } else {\n          $value = str_replace(","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/differential/conduit/DifferentialGetCommitMessageConduitAPIMethod.php#L90-L126","documentation":"differential.getcommitmessage iterates the commit-message field list and calls renderFieldValue($wire_value) on each field. The API contract requires every field to render either a string or null; if any field implementation returns another type (int, array, bool, object), the method throws a plain Exception naming the field key and the offending gettype(). This is a server-side invariant violation, almost always introduced by a custom commit-message field.","triggerScenarios":"Installing a custom Differential commit-message field (or an extension overriding renderFieldValue) that returns an integer, array, or boolean - for example returning a raw epoch timestamp or a list of reviewers instead of a formatted string. Every getcommitmessage call then fails for revisions using that field.","commonSituations":"Site administrators adding custom fields for release notes or reviewer lines; fields written for the object-rendering API where arrays are fine, reused for commit messages where they are not; upstream field regressions after an upgrade.","solutions":["Find the offending field: the exception text names the field key - match it against your custom field FIELDKEY constants","Fix renderFieldValue to return a string or null; format non-string data (implode arrays, cast scalars) before returning","Return null for 'nothing to render' instead of false or an empty array","Temporarily disable the custom field to confirm the diagnosis, then re-enable after the fix"],"exampleFix":"// before (custom field)\npublic function renderFieldValue($value) {\n  return $value; // may be int or array -> crashes getcommitmessage\n}\n\n// after\npublic function renderFieldValue($value) {\n  if ($value === null) {\n    return null;\n  }\n  if (is_array($value)) {\n    return implode(', ', $value);\n  }\n  return (string)$value;\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// Guard inside every custom commit-message field.\npublic function renderFieldValue($value) {\n  if ($value === null) {\n    return null;\n  }\n  if (!is_string($value)) {\n    if (is_array($value)) {\n      $value = implode(', ', $value);\n    } else {\n      $value = (string)$value;\n    }\n  }\n  return $value;\n}","tryCatchPattern":"// Server-side wrapper while diagnosing a misbehaving field:\n$value = $field->renderFieldValue($wire_value);\nif (!is_string($value) && !is_null($value)) {\n  phlog(pht('Field %s rendered %s; suppressing.',\n    $field->getFieldKey(), gettype($value)));\n  $value = null;\n}","preventionTips":["Treat string|null as the renderFieldValue contract and enforce it in code review","Unit-test custom fields by calling renderFieldValue with all wire shapes","After Phabricator upgrades, re-test every custom commit-message field"],"tags":["phabricator","conduit","differential","custom-fields","type-error"],"backgroundTag":"type-mismatch","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}