phacility/phabricator · error · Exception

%s '%s' has a patch '%s' which duplicates an existing patch

Error message

%s '%s' has a patch '%s' which duplicates an existing patch key.

What it means

Full patch keys ('namespace:key') must be globally unique across every installed patch list. When two patches resolve to the same full key, Phabricator cannot decide which one defines the schema state, so buildAllPatches() aborts. Note the duplicate is detected by full key, so two lists sharing one namespace, or one list repeating a key, both trip this.

Source

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

              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));
        }

        $namespace = $patch_list->getNamespace();
        $full_key = "{$namespace}:{$key}";

        if (isset($specs[$full_key])) {
          throw new Exception(
            pht(
              "%s '%s' has a patch '%s' which duplicates an ".
              "existing patch key.",
              __CLASS__,
              get_class($patch_list),
              $key));
        }

        $patch['key']     = $key;
        $patch['fullKey'] = $full_key;
        $patch['dead']    = (bool)idx($patch, 'dead', false);

        if (isset($patch['legacy'])) {
          if ($namespace != 'phabricator') {
            throw new Exception(
              pht(
                "Only patches in the '%s' namespace may contain '%s' keys.",
                'phabricator',

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Search the named patch list class for the reported key; if it appears twice, rename the newer entry.
  2. If you intended to copy an existing patch, give the new entry its own unique date-prefixed key.
  3. If two classes share a namespace, fix getNamespace() so each application/library has a distinct one.
  4. Re-run 'bin/storage status' to confirm uniqueness.

Example fix

// before
return array(
  '20180102.gadget.sql' => array(
    'type' => 'sql',
    'name' => $this->getPatchPath('20180102.gadget.sql'),
  ),
  '20180102.gadget.sql' => array(            // duplicate key
    'type' => 'sql',
    'name' => $this->getPatchPath('20180103.gadget.index.sql'),
  ),
);

// after
return array(
  '20180102.gadget.sql' => array(
    'type' => 'sql',
    'name' => $this->getPatchPath('20180102.gadget.sql'),
  ),
  '20180103.gadget.index.sql' => array(
    'type' => 'sql',
    'name' => $this->getPatchPath('20180103.gadget.index.sql'),
  ),
);
Defensive patterns

Strategy: validation

Validate before calling

// Detect duplicate full keys across every installed patch list before storage runs:
PhabricatorSQLPatchList::buildAllPatches(); // throws on duplicates
// Or, for a single list:
$keys = array_keys((new MyApplicationPatchList())->getPatches());
if (count($keys) !== count(array_unique($keys))) {
  throw new Exception('Duplicate patch keys in list');
}

Prevention

When it happens

Trigger: The same patch key appears twice in one getPatches() list (classic copy-paste: duplicating the previous entry and forgetting to change its key), or two PhabricatorSQLPatchList subclasses declare the same getNamespace() with overlapping keys.

Common situations: Copy-pasting the most recent patch entry to start a new one and forgetting to rename the key; merging two branches that both added a patch with the same date-prefixed name; accidentally registering two patch list classes with the same namespace.

Related errors


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