phacility/phabricator · error · Exception
Expected nonempty '%s' specification!
Error message
Expected nonempty '%s' specification!
What it means
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.
Source
Thrown at src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php:73
'lookup' => true,
'protocaps' => true,
'stream_out' => true,
);
// Notably, the write commands are "pushkey" and "unbundle". The
// "batch" command is theoretically read only, but we require explicit
// analysis of the actual commands.
return isset($read_only[$command]);
}
public static function isReadOnlyBatchCommand($cmds) {
if (!strlen($cmds)) {
// We expect a "batch" command to always have a "cmds" string, so err
// on the side of caution and throw if we don't get any data here. This
// either indicates a mangled command from the client or a programming
// error in our code.
throw new Exception(pht("Expected nonempty '%s' specification!", 'cmds'));
}
// For "batch" we get a "cmds" argument like:
//
// heads ;known nodes=
//
// We need to examine the commands (here, "heads" and "known") to make sure
// they're all read-only.
// NOTE: Mercurial has some code to escape semicolons, but it does not
// actually function for command separation. For example, these two batch
// commands will produce completely different results (the former will run
// the lookup; the latter will fail with a parser error):
//
// lookup key=a:xb;lookup key=z* 0
// lookup key=a:;b;lookup key=z* 0
// ^
// |View on GitHub (pinned to 5720a38cfe)
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
Defensive patterns
Strategy: validation
Validate before calling
// validate the batch payload before read-only classification
$cmds = idx($argv, 'cmds', '');
if (!is_string($cmds) || !strlen($cmds)) {
// reject the mangled request up front with a protocol error
} Type guard
function isValidBatchSpec($cmds) {
return is_string($cmds) && strlen($cmds) > 0;
} Try / catch
try {
$read_only = DiffusionMercurialWireProtocol::isReadOnlyBatchCommand($cmds);
} catch (Exception $ex) {
// convert to a protocol-level error for the client and keep the daemon alive
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Unknown Mercurial command "%s"!
- Mercurial command appears to contain unsafe injected "--conf
- Unexpected command structure, expected '%s'.
- Mercurial does not currently support searching for ancestors
- Unexpected line count from `%s`!
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/c9f8edc3c89d22d5.
Report an issue: GitHub.