I am writing a shell script in Haskell using turtle and would like to know best practices on composing commands that could fail.
Now I have a case expression staircase, like so:
runRemote :: MonadIO io => Text -> Text -> io ()
runRemote oldVersion' newVersion' = sh $ do
  mkdir "out"
  e1 <- shell ("command " <> oldVersion') empty
  case e1 of
    ExitFailure n -> cleanup 
    ExitSuccess -> do
      e2 <- shell ("command " <> newVersion') empty
      case e2 of
        ExitFailure n -> cleanup 
        ExitSuccess -> do
          curDir <- pwd
          cd (curDir <.> oldVersion')
          e3 <- shell ("command something else") empty
          case e3 of
           -- ...
           -- And so on...
If the case expression was expanding on a Maybe type, the solution would be to derive a Monad instance. 
Is there a special reason the library author didn't already derive a Monad instance for ExitCode or is there a better way to do error handling for Haskell shell code?