diff --git a/mercurial/commandserver.py b/mercurial/commandserver.py
index bc374458688b4d53bf0b0387735dc859d1448a03_bWVyY3VyaWFsL2NvbW1hbmRzZXJ2ZXIucHk=..69f86b9370350a939ed415f49e2ff110085d447d_bWVyY3VyaWFsL2NvbW1hbmRzZXJ2ZXIucHk= 100644
--- a/mercurial/commandserver.py
+++ b/mercurial/commandserver.py
@@ -7,7 +7,7 @@
 
 from i18n import _
 import struct
-import os, errno, traceback, SocketServer
+import sys, os, errno, traceback, SocketServer
 import dispatch, encoding, util
 
 logfile = None
@@ -248,6 +248,29 @@
 
         return 0
 
+def _protectio(ui):
+    """ duplicates streams and redirect original to null if ui uses stdio """
+    ui.flush()
+    newfiles = []
+    nullfd = os.open(os.devnull, os.O_RDWR)
+    for f, sysf, mode in [(ui.fin, sys.stdin, 'rb'),
+                          (ui.fout, sys.stdout, 'wb')]:
+        if f is sysf:
+            newfd = os.dup(f.fileno())
+            os.dup2(nullfd, f.fileno())
+            f = os.fdopen(newfd, mode)
+        newfiles.append(f)
+    os.close(nullfd)
+    return tuple(newfiles)
+
+def _restoreio(ui, fin, fout):
+    """ restores streams from duplicated ones """
+    ui.flush()
+    for f, uif in [(fin, ui.fin), (fout, ui.fout)]:
+        if f is not uif:
+            os.dup2(f.fileno(), uif.fileno())
+            f.close()
+
 class pipeservice(object):
     def __init__(self, ui, repo, opts):
         self.ui = ui
@@ -258,8 +281,14 @@
 
     def run(self):
         ui = self.ui
-        sv = server(ui, self.repo, ui.fin, ui.fout)
-        return sv.serve()
+        # redirect stdio to null device so that broken extensions or in-process
+        # hooks will never cause corruption of channel protocol.
+        fin, fout = _protectio(ui)
+        try:
+            sv = server(ui, self.repo, fin, fout)
+            return sv.serve()
+        finally:
+            _restoreio(ui, fin, fout)
 
 class _requesthandler(SocketServer.StreamRequestHandler):
     def handle(self):
diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
index bc374458688b4d53bf0b0387735dc859d1448a03_dGVzdHMvdGVzdC1jb21tYW5kc2VydmVyLnQ=..69f86b9370350a939ed415f49e2ff110085d447d_dGVzdHMvdGVzdC1jb21tYW5kc2VydmVyLnQ= 100644
--- a/tests/test-commandserver.t
+++ b/tests/test-commandserver.t
@@ -492,6 +492,7 @@
   foo
 
   $ cat <<EOF > dbgui.py
+  > import os, sys
   > from mercurial import cmdutil, commands
   > cmdtable = {}
   > command = cmdutil.command(cmdtable)
@@ -501,6 +502,14 @@
   > @command("debugprompt", norepo=True)
   > def debugprompt(ui):
   >     ui.write("%s\\n" % ui.prompt("prompt:"))
+  > @command("debugreadstdin", norepo=True)
+  > def debugreadstdin(ui):
+  >     ui.write("read: %r\n" % sys.stdin.read(1))
+  > @command("debugwritestdout", norepo=True)
+  > def debugwritestdout(ui):
+  >     os.write(1, "low-level stdout fd and\n")
+  >     sys.stdout.write("stdout should be redirected to /dev/null\n")
+  >     sys.stdout.flush()
   > EOF
   $ cat <<EOF >> .hg/hgrc
   > [extensions]
@@ -518,7 +527,9 @@
   ...     runcommand(server, ['debugprompt', '--config',
   ...                         'ui.interactive=True'],
   ...                input=cStringIO.StringIO('5678\n'))
+  ...     runcommand(server, ['debugreadstdin'])
+  ...     runcommand(server, ['debugwritestdout'])
   *** runcommand debuggetpass --config ui.interactive=True
   password: 1234
   *** runcommand debugprompt --config ui.interactive=True
   prompt: 5678
@@ -521,7 +532,10 @@
   *** runcommand debuggetpass --config ui.interactive=True
   password: 1234
   *** runcommand debugprompt --config ui.interactive=True
   prompt: 5678
+  *** runcommand debugreadstdin
+  read: ''
+  *** runcommand debugwritestdout
 
 
 run commandserver in commandserver, which is silly but should work: