phacility/phabricator · error · Exception

Patch '%s' is missing key 'after', and is the first patch in

Error message

Patch '%s' is missing key 'after', and is the first patch in the patch list '%s', so its application order can not be determined implicitly. The first patch in a patch list must list the patch or patches it depends on explicitly.

What it means

For convenience, a patch without an 'after' key is implicitly ordered after the previous patch in the same namespace and phase. That inference is impossible for the very first patch in the default phase of a patch list (there is no previous patch), so the loader requires its dependency list to be explicit to keep upgrade order deterministic.

Source

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

        }

        $patch_phase = $patch['phase'];

        if (!isset($phases[$patch_phase])) {
          throw new Exception(
            pht(
              'Storage patch "%s" specifies it should apply in phase "%s", '.
              'but this phase is unrecognized. Valid phases are: %s.',
              $full_key,
              $patch_phase,
              implode(', ', array_keys($phases))));
        }

        $last_key = $last_keys[$patch_phase];

        if (!array_key_exists('after', $patch)) {
          if ($last_key === null && $patch_phase === $default_phase) {
            throw new Exception(
              pht(
                "Patch '%s' is missing key 'after', and is the first patch ".
                "in the patch list '%s', so its application order can not be ".
                "determined implicitly. The first patch in a patch list must ".
                "list the patch or patches it depends on explicitly.",
                $full_key,
                get_class($patch_list)));
          } 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) {

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Add an explicit 'after' key to the first patch: array() if it truly depends on nothing, or the full keys of patches it must follow (e.g. a phabricator patch your schema builds on).
  2. Every subsequent patch may then omit 'after' and inherit implicit ordering.
  3. Re-run 'bin/storage status' to confirm the list loads.

Example fix

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

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

Strategy: validation

Validate before calling

// The very first patch of each list must declare 'after' explicitly:
$patches = (new MyApplicationPatchList())->getPatches();
$first = array_slice($patches, 0, 1, true);
foreach ($first as $key => $patch) {
  if (!array_key_exists('after', $patch)) {
    throw new Exception("First patch '{$key}' must declare an explicit 'after'");
  }
}

Prevention

When it happens

Trigger: The first entry of an application's getPatches() return value (in the default phase) has no 'after' key. Typically this is the bootstrap patch of a brand-new application or extension.

Common situations: Creating the first storage patch for a new Phabricator application; splitting an existing patch list into a new namespace whose first patch was written without dependencies.

Related errors


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