phacility/phabricator · error · Exception

Mail commands "%s" and "%s" both respond to keyword "%s". Ke

Error message

Mail commands "%s" and "%s" both respond to keyword "%s". Keywords must be uniquely associated with commands.

What it means

MetaMTAEmailTransactionCommand::getCommandMap() builds the reply-command keyword table: for every registered command it indexes the primary getCommand() keyword plus every getCommandAliases() alias, lowercased. Two distinct command classes claimed the same keyword, so the mapping would be ambiguous and Phabricator throws instead of silently letting one shadow the other. The exception names both class names and the colliding keyword.

Source

Thrown at src/applications/metamta/command/MetaMTAEmailTransactionCommand.php:96

    }

    return $commands;
  }

  public static function getCommandMap(array $commands) {
    assert_instances_of($commands, __CLASS__);

    $map = array();
    foreach ($commands as $command) {
      $keywords = $command->getCommandAliases();
      $keywords[] = $command->getCommand();

      foreach ($keywords as $keyword) {
        $keyword = phutil_utf8_strtolower($keyword);
        if (empty($map[$keyword])) {
          $map[$keyword] = $command;
        } else {
          throw new Exception(
            pht(
              'Mail commands "%s" and "%s" both respond to keyword "%s". '.
              'Keywords must be uniquely associated with commands.',
              get_class($command),
              get_class($map[$keyword]),
              $keyword));
        }
      }
    }

    return $map;
  }

}

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Read the exception: it names both conflicting classes and the keyword - rename or remove the alias in the class you control (getCommandAliases()/getCommand()).
  2. If the collision is with an upstream command, choose a distinct keyword rather than disabling core.
  3. Clear caches ('bin/cache clear') after the fix so the command map is rebuilt, then verify by processing a test reply ('bin/mail receive-test').

Example fix

// before: custom command collides with Maniphest's 'claim'
public function getCommand() { return 'claim'; }

// after: distinct keyword, no shadowing
public function getCommand() { return 'take'; }
public function getCommandAliases() { return array(); }
Defensive patterns

Strategy: validation

Validate before calling

// Detect keyword collisions before the mail pipeline does.
$seen = array();
foreach (MetaMTAEmailTransactionCommand::getAllCommands() as $command) {
  $keywords = array_merge($command->getCommandAliases(), array($command->getCommand()));
  foreach ($keywords as $keyword) {
    $keyword = phutil_utf8_strtolower($keyword);
    if (isset($seen[$keyword])) {
      throw new Exception(sprintf(
        'Mail command keyword "%s" claimed by both %s and %s.',
        $keyword, $seen[$keyword], get_class($command)));
    }
    $seen[$keyword] = get_class($command);
  }
}

Prevention

When it happens

Trigger: Enabling an application or extension whose mail command (or one of its aliases) collides with an existing one - e.g. a custom command registering keyword 'claim' while Maniphest's claim command already owns it; also a command class that lists an alias identical to its own primary keyword collides with itself. The map is built whenever reply-handler command parsing initializes (inbound mail processing, command docs).

Common situations: Third-party or local extensions after an upstream upgrade introduces the same keyword; two extensions registering overlapping aliases; adding a new reply command without checking the keyword table first.

Related errors


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