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
- Read the exception: it names both conflicting classes and the keyword - rename or remove the alias in the class you control (getCommandAliases()/getCommand()).
- If the collision is with an upstream command, choose a distinct keyword rather than disabling core.
- 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 adding a mail command, grep existing command classes for your keyword and aliases first.
- Keep getCommandAliases() minimal - every alias is another collision opportunity.
- Add the collision check above to extension test suites so conflicts surface at CI time, not at inbound-mail time.
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
- Adapter ("%s") is configured for medium "%s", but this is no
- Mailer ("%s") is attempting to access unknown option ("%s").
- Failed to JSON decode response.
- Request failed with errors: %s.
- Unit Test (Permanent)
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/ac5ca5fc9059d17a.
Report an issue: GitHub.