{"record":{"id":"4e11085905ba7d15","repo":"phacility/phabricator","slug":"transaction-value-when-setting-almanac-properties","errorCode":null,"errorMessage":"Transaction value when setting Almanac properties must be a map with property names as keys.","messagePattern":"Transaction value when setting Almanac properties must be a map with property names as keys\\.","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/almanac/engineextension/AlmanacSetPropertyEditType.php","lineNumber":12,"sourceCode":"<?php\n\nfinal class AlmanacSetPropertyEditType\n  extends PhabricatorEditType {\n\n  public function generateTransactions(\n    PhabricatorApplicationTransaction $template,\n    array $spec) {\n\n    $value = idx($spec, 'value');\n    if (!is_array($value)) {\n      throw new Exception(\n        pht(\n          'Transaction value when setting Almanac properties must be a map '.\n          'with property names as keys.'));\n    }\n\n    $xactions = array();\n    foreach ($value as $property_key => $property_value) {\n      $xactions[] = $this->newTransaction($template)\n        ->setMetadataValue('almanac.property', $property_key)\n        ->setNewValue($property_value);\n    }\n\n    return $xactions;\n  }\n\n}\n","sourceCodeStart":1,"sourceCodeEnd":29,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/almanac/engineextension/AlmanacSetPropertyEditType.php#L1-L29","documentation":"Thrown by AlmanacSetPropertyEditType::generateTransactions() when a 'property.set' transaction's 'value' is not an array. Unlike property.delete (which wants a list), property.set wants a map of property name => value, e.g. {\"almanac.props.foo\": \"bar\"}. Each map entry becomes one transaction with the key stored in 'almanac.property' metadata and the map value as the new value.","triggerScenarios":"Calling almanac.device.edit / almanac.service.edit with {\"type\":\"property.set\",\"value\":[\"almanac.props.foo\"]} (a list, as property.delete uses); passing a bare string \"almanac.props.foo=bar\"; passing null; sending a JSON-encoded string instead of an object.","commonSituations":"Symmetry confusion between property.set (map) and property.delete (list) in the same edit request; clients that cannot emit empty objects and send \"[]\" or \"null\"; hand-built query strings that lose the object shape.","solutions":["Send value as an object/map keyed by property name: {\"almanac.props.foo\": \"bar\"}.","For setting multiple properties, put all keys in one map: {\"almanac.props.a\": 1, \"almanac.props.b\": 2}.","To REMOVE properties, switch the transaction type to property.delete with a list of names instead of abusing property.set."],"exampleFix":"// before\n array('type' => 'property.set', 'value' => array('almanac.props.custom')),\n\n// after\n array('type' => 'property.set',\n       'value' => array('almanac.props.custom' => 'some-value')))","handlingStrategy":"validation","validationCode":"// property.set wants map<string,mixed>\nif (!is_array($value) || ($value !== [] && array_keys($value) === range(0, count($value) - 1))) {\n  throw new InvalidArgumentException(\n    'property.set value must be a map of property name => value');\n}","typeGuard":"function is_property_set_map(mixed $v): bool {\n  return is_array($v)\n    && ($v === [] || !array_is_list($v));\n}","tryCatchPattern":null,"preventionTips":["Keep a typed wrapper for each edit-engine transaction type so shape mistakes are impossible at call sites.","Remember deleting uses a LIST, setting uses a MAP; document both next to each other.","When serializing from languages without distinct list/map types (Python, old PHP), assert keys are strings before submission."],"tags":["phabricator","almanac","conduit","edit-engine","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}