{"record":{"id":"c9f8edc3c89d22d5","repo":"phacility/phabricator","slug":"expected-nonempty-s-specification","errorCode":null,"errorMessage":"Expected nonempty '%s' specification!","messagePattern":"Expected nonempty '(.+?)' specification!","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php","lineNumber":73,"sourceCode":"      'lookup' => true,\n      'protocaps' => true,\n      'stream_out' => true,\n    );\n\n    // Notably, the write commands are \"pushkey\" and \"unbundle\". The\n    // \"batch\" command is theoretically read only, but we require explicit\n    // analysis of the actual commands.\n\n    return isset($read_only[$command]);\n  }\n\n  public static function isReadOnlyBatchCommand($cmds) {\n    if (!strlen($cmds)) {\n      // We expect a \"batch\" command to always have a \"cmds\" string, so err\n      // on the side of caution and throw if we don't get any data here. This\n      // either indicates a mangled command from the client or a programming\n      // error in our code.\n      throw new Exception(pht(\"Expected nonempty '%s' specification!\", 'cmds'));\n    }\n\n    // For \"batch\" we get a \"cmds\" argument like:\n    //\n    //   heads ;known nodes=\n    //\n    // We need to examine the commands (here, \"heads\" and \"known\") to make sure\n    // they're all read-only.\n\n    // NOTE: Mercurial has some code to escape semicolons, but it does not\n    // actually function for command separation. For example, these two batch\n    // commands will produce completely different results (the former will run\n    // the lookup; the latter will fail with a parser error):\n    //\n    //  lookup key=a:xb;lookup key=z* 0\n    //  lookup key=a:;b;lookup key=z* 0\n    //               ^\n    //               |","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php#L55-L91","documentation":"For the Mercurial wire 'batch' command, Phabricator receives a 'cmds' argument (for example 'heads;known nodes=') and must inspect every sub-command to decide whether the batch is read-only. An empty or missing cmds string cannot be analyzed, so isReadOnlyBatchCommand() deliberately throws rather than assume the batch is safe: the comment says this indicates either a mangled command from the client or a programming error in Phabricator itself.","triggerScenarios":"A client sends 'batch' over the ssh protocol endpoint with a missing, empty, or truncated 'cmds' value, for example a hand-written protocol client, ssh stdin corruption, or a Phabricator parsing regression that drops the argument.","commonSituations":"Custom hg wrappers or CI scripts speaking the raw wire protocol; proxies or locale/terminal mangling corrupting ssh stdin; Phabricator protocol-parsing bugs after upgrades.","solutions":["Reproduce with a stock, unmodified hg client; if stock works, capture the exact bytes your custom client sends and fix the 'cmds' payload","Check for ssh stdin corruption between client and server (proxies, wrapper scripts, unusual environment)","If a stock client triggers it, file a Phabricator bug with the full command trace"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// validate the batch payload before read-only classification\n$cmds = idx($argv, 'cmds', '');\nif (!is_string($cmds) || !strlen($cmds)) {\n  // reject the mangled request up front with a protocol error\n}","typeGuard":"function isValidBatchSpec($cmds) {\n  return is_string($cmds) && strlen($cmds) > 0;\n}","tryCatchPattern":"try {\n  $read_only = DiffusionMercurialWireProtocol::isReadOnlyBatchCommand($cmds);\n} catch (Exception $ex) {\n  // convert to a protocol-level error for the client and keep the daemon alive\n}","preventionTips":["Never dispatch 'batch' without asserting the cmds parameter is present and non-empty","Log the raw command line whenever this fires: the payload is the only evidence of what mangled it"],"tags":["mercurial","wire-protocol","batch","input-validation"],"backgroundTag":"empty-request-parameter","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}