diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php
--- a/src/applications/diffusion/controller/DiffusionServeController.php
+++ b/src/applications/diffusion/controller/DiffusionServeController.php
@@ -878,10 +878,29 @@
       }
       $args_raw[] = $_SERVER[$header];
     }
-    $args_raw = implode('', $args_raw);
 
-    return id(new PhutilQueryStringParser())
-      ->parseQueryString($args_raw);
+    if ($args_raw) {
+      $args_raw = implode('', $args_raw);
+      return id(new PhutilQueryStringParser())
+        ->parseQueryString($args_raw);
+    }
+
+    // Sometimes arguments come in via the query string. Note that this will
+    // not handle multi-value entries e.g. "a[]=1,a[]=2" however it's unclear
+    // whether or how the mercurial protocol should handle this.
+    $query = idx($_SERVER, 'QUERY_STRING', '');
+    $query_pairs = id(new PhutilQueryStringParser())
+      ->parseQueryString($query);
+    foreach ($query_pairs as $key => $value) {
+      // Filter out private/internal keys as well as the command itself.
+      if (strncmp($key, '__', 2) && $key != 'cmd') {
+        $args_raw[$key] = $value;
+      }
+    }
+
+    // TODO: Arguments can also come in via request body for POST requests. The
+    // body would be all arguments, url-encoded.
+    return $args_raw;
   }
 
   private function formatMercurialArguments($command, array $arguments) {