phacility/phabricator · error · Exception

%s '%s' has a patch with a numeric key, '%s'. Patches must u

Error message

%s '%s' has a patch with a numeric key, '%s'. Patches must use string keys.

What it means

Every patch in a PhabricatorSQLPatchList must be keyed by a descriptive string (conventionally 'YYYYMMDD.short.name.sql'). String keys become the patch's permanent identity ('namespace:key') used for ordering, dependency tracking, and remembering which patches are already applied. Numeric keys mean the entry has no usable identity, so loading the list fails.

Source

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

          'phase' => true,
        );

        foreach ($patch as $pkey => $pval) {
          if (empty($valid[$pkey])) {
            throw new Exception(
              pht(
                "%s '%s' has a patch, '%s', with an unknown property, '%s'.".
                "Patches must have only valid keys: %s.",
                __CLASS__,
                get_class($patch_list),
                $key,
                $pkey,
                implode(', ', array_keys($valid))));
          }
        }

        if (is_numeric($key)) {
          throw new Exception(
            pht(
              "%s '%s' has a patch with a numeric key, '%s'. ".
              "Patches must use string keys.",
              __CLASS__,
              get_class($patch_list),
              $key));
        }

        if (strpos($key, ':') !== false) {
          throw new Exception(
            pht(
              "%s '%s' has a patch with a colon in the key name, '%s'. ".
              "Patch keys may not contain colons.",
              __CLASS__,
              get_class($patch_list),
              $key));
        }

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Give the offending patch a full conventional string key: 'YYYYMMDD.descriptive.name.sql'.
  2. Make sure the key contains non-digit characters (a dot alone is enough) so PHP keeps it a string.
  3. Replace any array_merge() on patch arrays with the '+' operator to avoid integer-key renumbering.
  4. Re-run 'bin/storage status' to verify the list parses.

Example fix

// before
return array(
  array(                        // no key -> numeric key 0
    'type' => 'sql',
    'name' => $this->getPatchPath('20180101.widget.sql'),
  ),
);

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

Strategy: validation

Validate before calling

// Assert every key in your patch map is a non-numeric string:
foreach ((new MyApplicationPatchList())->getPatches() as $key => $spec) {
  if (is_numeric($key) || !is_string($key)) {
    throw new Exception("Patch key must be a descriptive string: {$key}");
  }
}

Prevention

When it happens

Trigger: getPatches() returns an entry with an integer key — most often because the array key was omitted entirely ('array(...)' instead of "'20180101.foo.sql' => array(...)"), because the key is pure digits with no letters (e.g. '0000' or '20180101', which PHP treats as an integer), or because the list was combined with array_merge() which renumbers integer keys.

Common situations: Writing the first patch of a new application and using a bare date as the key; merging patch arrays with array_merge() (use '+' instead so string keys are preserved); refactoring a hand-written list into buildPatchesFromDirectory() output and losing keys.

Related errors


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