diff --git a/rust/hgcli/src/main.rs b/rust/hgcli/src/main.rs
--- a/rust/hgcli/src/main.rs
+++ b/rust/hgcli/src/main.rs
@@ -9,7 +9,7 @@
 extern crate cpython;
 extern crate python27_sys;
 
-use cpython::{NoArgs, ObjectProtocol, PyModule, PyResult, Python};
+use cpython::{NoArgs, ObjectProtocol, PyModule, PyObject, PyResult, Python};
 use libc::{c_char, c_int};
 
 use std::env;
@@ -190,13 +190,21 @@
         // Investigate if we can intercept sys.exit() or SystemExit() to
         // ensure we handle process exit.
         result = match run_py(&env, py) {
-            // Print unhandled exceptions and exit code 255, as this is what
+            // Print unhandled exceptions and exit code 1, as this is what
             // `python` does.
             Err(err) => {
                 err.print(py);
-                Err(255)
+                Err(1)
             }
-            Ok(()) => Ok(()),
+            Ok(value) => {
+                // dispatch.run() should return an integer exit code
+                // between 0 and 255, inclusive.
+                let code = match value.extract::<i32>(py) {
+                    Ok(value) => value,
+                    Err(_) => panic!("non-integer value returned by dispatch.run()"),
+                };
+                Err(code)
+            }
         };
     }
 
@@ -207,7 +215,7 @@
     result
 }
 
-fn run_py(env: &Environment, py: Python) -> PyResult<()> {
+fn run_py(env: &Environment, py: Python) -> PyResult<PyObject> {
     let sys_mod = py.import("sys").unwrap();
 
     update_encoding(py, &sys_mod);
@@ -218,9 +226,7 @@
     demand_mod.call(py, "enable", NoArgs, None)?;
 
     let dispatch_mod = py.import("mercurial.dispatch")?;
-    dispatch_mod.call(py, "run", NoArgs, None)?;
-
-    Ok(())
+    dispatch_mod.call(py, "run", NoArgs, None)
 }
 
 fn main() {