{"record":{"id":"2b513f2af1c9acb6","repo":"phacility/phabricator","slug":"patch-s-is-missing-key-s-every-patch-must-h","errorCode":null,"errorMessage":"Patch '%s' is missing key '%s'. Every patch must have a type.","messagePattern":"Patch '(.+?)' is missing key '(.+?)'\\. Every patch must have a type\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/infrastructure/storage/patch/PhabricatorSQLPatchList.php","lineNumber":196,"sourceCode":"          } else {\n            if ($last_key === null) {\n              $patch['after'] = array();\n            } else {\n              $patch['after'] = array($last_key);\n            }\n          }\n        }\n        $last_keys[$patch_phase] = $full_key;\n\n        foreach ($patch['after'] as $after_key => $after) {\n          if (strpos($after, ':') === false) {\n            $patch['after'][$after_key] = $namespace.':'.$after;\n          }\n        }\n\n        $type = idx($patch, 'type');\n        if (!$type) {\n          throw new Exception(\n            pht(\n              \"Patch '%s' is missing key '%s'. Every patch must have a type.\",\n              \"{$namespace}:{$key}\",\n              'type'));\n        }\n\n        switch ($type) {\n          case 'db':\n          case 'sql':\n          case 'php':\n            break;\n          default:\n            throw new Exception(\n              pht(\n                \"Patch '%s' has unknown patch type '%s'.\",\n                \"{$namespace}:{$key}\",\n                $type));\n        }","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/infrastructure/storage/patch/PhabricatorSQLPatchList.php#L178-L214","documentation":"Every patch must declare how it executes via a 'type' key, because the storage engine dispatches on it: 'db' creates a database, 'sql' runs a .sql file, 'php' runs a PHP script. A patch without a type cannot be applied, so the loader rejects the whole list.","triggerScenarios":"A patch entry in getPatches() omits 'type' or sets it to a falsy value ('' or null). Common when hand-writing an entry and listing only 'name' and 'after'.","commonSituations":"Writing the first manual patch after switching away from buildPatchesFromDirectory() (which fills 'type' automatically); trimming a patch entry down during a refactor and dropping the key.","solutions":["Add 'type' => 'sql' (or 'php' / 'db') to the patch entry named in the message.","Match the type to the file: .sql files -> 'sql', PHP migration scripts -> 'php', bare database creation -> 'db'.","Re-run 'bin/storage status' to confirm the list loads."],"exampleFix":"// before\n'20180101.widget.sql' => array(\n  'name' => $this->getPatchPath('20180101.widget.sql'),\n),\n\n// after\n'20180101.widget.sql' => array(\n  'type' => 'sql',\n  'name' => $this->getPatchPath('20180101.widget.sql'),\n),","handlingStrategy":"validation","validationCode":"// Check every patch declares one of the three known types:\nforeach ((new MyApplicationPatchList())->getPatches() as $key => $patch) {\n  if (empty($patch['type'])) {\n    throw new Exception(\"Patch '{$key}' is missing its 'type' key\");\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Prefer buildPatchesFromDirectory() over hand-written entries — it fills in 'type' and 'name' from the file extension.","When writing entries by hand, add 'type' in the same edit as 'name'."],"tags":["phabricator","database-migration","patch-type","php"],"backgroundTag":"database-migration-validation","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}