{"record":{"id":"ac5ca5fc9059d17a","repo":"phacility/phabricator","slug":"mail-commands-s-and-s-both-respond-to-keywor","errorCode":null,"errorMessage":"Mail commands \"%s\" and \"%s\" both respond to keyword \"%s\". Keywords must be uniquely associated with commands.","messagePattern":"Mail commands \"(.+?)\" and \"(.+?)\" both respond to keyword \"(.+?)\"\\. Keywords must be uniquely associated with commands\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/metamta/command/MetaMTAEmailTransactionCommand.php","lineNumber":96,"sourceCode":"    }\n\n    return $commands;\n  }\n\n  public static function getCommandMap(array $commands) {\n    assert_instances_of($commands, __CLASS__);\n\n    $map = array();\n    foreach ($commands as $command) {\n      $keywords = $command->getCommandAliases();\n      $keywords[] = $command->getCommand();\n\n      foreach ($keywords as $keyword) {\n        $keyword = phutil_utf8_strtolower($keyword);\n        if (empty($map[$keyword])) {\n          $map[$keyword] = $command;\n        } else {\n          throw new Exception(\n            pht(\n              'Mail commands \"%s\" and \"%s\" both respond to keyword \"%s\". '.\n              'Keywords must be uniquely associated with commands.',\n              get_class($command),\n              get_class($map[$keyword]),\n              $keyword));\n        }\n      }\n    }\n\n    return $map;\n  }\n\n}\n","sourceCodeStart":78,"sourceCodeEnd":111,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/metamta/command/MetaMTAEmailTransactionCommand.php#L78-L111","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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')."],"exampleFix":"// before: custom command collides with Maniphest's 'claim'\npublic function getCommand() { return 'claim'; }\n\n// after: distinct keyword, no shadowing\npublic function getCommand() { return 'take'; }\npublic function getCommandAliases() { return array(); }","handlingStrategy":"validation","validationCode":"// Detect keyword collisions before the mail pipeline does.\n$seen = array();\nforeach (MetaMTAEmailTransactionCommand::getAllCommands() as $command) {\n  $keywords = array_merge($command->getCommandAliases(), array($command->getCommand()));\n  foreach ($keywords as $keyword) {\n    $keyword = phutil_utf8_strtolower($keyword);\n    if (isset($seen[$keyword])) {\n      throw new Exception(sprintf(\n        'Mail command keyword \"%s\" claimed by both %s and %s.',\n        $keyword, $seen[$keyword], get_class($command)));\n    }\n    $seen[$keyword] = get_class($command);\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["phabricator","metamta","mail-commands","duplicate-registration","extension"],"backgroundTag":"duplicate-command-registration","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}