phacility/phabricator · error · Exception

Patch '%s' is missing key '%s'. Every patch must have a type

Error message

Patch '%s' is missing key '%s'. Every patch must have a type.

What it means

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.

Source

Thrown at src/infrastructure/storage/patch/PhabricatorSQLPatchList.php:196

          } else {
            if ($last_key === null) {
              $patch['after'] = array();
            } else {
              $patch['after'] = array($last_key);
            }
          }
        }
        $last_keys[$patch_phase] = $full_key;

        foreach ($patch['after'] as $after_key => $after) {
          if (strpos($after, ':') === false) {
            $patch['after'][$after_key] = $namespace.':'.$after;
          }
        }

        $type = idx($patch, 'type');
        if (!$type) {
          throw new Exception(
            pht(
              "Patch '%s' is missing key '%s'. Every patch must have a type.",
              "{$namespace}:{$key}",
              'type'));
        }

        switch ($type) {
          case 'db':
          case 'sql':
          case 'php':
            break;
          default:
            throw new Exception(
              pht(
                "Patch '%s' has unknown patch type '%s'.",
                "{$namespace}:{$key}",
                $type));
        }

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Add 'type' => 'sql' (or 'php' / 'db') to the patch entry named in the message.
  2. Match the type to the file: .sql files -> 'sql', PHP migration scripts -> 'php', bare database creation -> 'db'.
  3. Re-run 'bin/storage status' to confirm the list loads.

Example fix

// before
'20180101.widget.sql' => array(
  'name' => $this->getPatchPath('20180101.widget.sql'),
),

// after
'20180101.widget.sql' => array(
  'type' => 'sql',
  'name' => $this->getPatchPath('20180101.widget.sql'),
),
Defensive patterns

Strategy: validation

Validate before calling

// Check every patch declares one of the three known types:
foreach ((new MyApplicationPatchList())->getPatches() as $key => $patch) {
  if (empty($patch['type'])) {
    throw new Exception("Patch '{$key}' is missing its 'type' key");
  }
}

Prevention

When it happens

Trigger: 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'.

Common situations: 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.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/2b513f2af1c9acb6. Report an issue: GitHub.