diff --git a/hspec-meta.cabal b/hspec-meta.cabal
--- a/hspec-meta.cabal
+++ b/hspec-meta.cabal
@@ -1,5 +1,5 @@
 name:             hspec-meta
-version:          1.10.0
+version:          1.11.0
 license:          MIT
 license-file:     LICENSE
 copyright:        (c) 2011-2014 Simon Hengel,
@@ -46,8 +46,8 @@
     , HUnit         >= 1.2.5
     , QuickCheck    >= 2.5.1
     , quickcheck-io
-    , async >= 2
     , hspec-expectations
+    , async >= 2
   exposed-modules:
       Test.Hspec.Meta
   other-modules:
@@ -65,6 +65,7 @@
       Test.Hspec.Options
       Test.Hspec.FailureReport
       Test.Hspec.Runner.Eval
+      Test.Hspec.Runner.Tree
       Test.Hspec.Formatters.Internal
       Test.Hspec.Timer
 
diff --git a/src/Test/Hspec.hs b/src/Test/Hspec.hs
--- a/src/Test/Hspec.hs
+++ b/src/Test/Hspec.hs
@@ -25,6 +25,7 @@
 , after
 , around
 , parallel
+, runIO
 
 -- * Running a spec
 , hspec
@@ -40,7 +41,7 @@
 
 -- | Combine a list of specs into a larger spec.
 describe :: String -> Spec -> Spec
-describe label action = fromSpecList [Core.describe label (runSpecM action)]
+describe label action = fromSpecList [Core.describe label [BuildSpecs $ runSpecM action]]
 
 -- | An alias for `describe`.
 context :: String -> Spec -> Spec
diff --git a/src/Test/Hspec/Core/Type.hs b/src/Test/Hspec/Core/Type.hs
--- a/src/Test/Hspec/Core/Type.hs
+++ b/src/Test/Hspec/Core/Type.hs
@@ -17,13 +17,16 @@
 , it
 , forceResult
 
+, runIO
+
 , pending
 , pendingWith
 ) where
 
 import qualified Control.Exception as E
 import           Control.Applicative
-import           Control.Monad.Trans.Writer (Writer, execWriter, tell)
+import           Control.Monad.Trans.Writer
+import           Control.Monad.IO.Class (liftIO)
 import           Data.Typeable (Typeable)
 import           Data.List (isPrefixOf)
 import           Data.Maybe (fromMaybe)
@@ -43,17 +46,26 @@
 type Spec = SpecM ()
 
 -- | A writer monad for `SpecTree` forests.
-newtype SpecM a = SpecM (Writer [SpecTree] a)
+newtype SpecM a = SpecM (WriterT [SpecTree] IO a)
   deriving (Functor, Applicative, Monad)
 
 -- | Convert a `Spec` to a forest of `SpecTree`s.
-runSpecM :: Spec -> [SpecTree]
-runSpecM (SpecM specs) = execWriter specs
+runSpecM :: Spec -> IO [SpecTree]
+runSpecM (SpecM specs) = execWriterT specs
 
 -- | Create a `Spec` from a forest of `SpecTree`s.
 fromSpecList :: [SpecTree] -> Spec
 fromSpecList = SpecM . tell
 
+-- | Run an IO action while constructing the spec tree.
+--
+-- `SpecM` is a monad to construct a spec tree, without executing any spec
+-- items.  `runIO` allows you to run IO actions during this construction phase.
+-- The IO action is always run when the spec tree is constructed (e.g. even
+-- when @--dry-run@ is specified).
+runIO :: IO a -> SpecM a
+runIO = SpecM . liftIO
+
 -- | The result of running an example.
 data Result = Success | Pending (Maybe String) | Fail String
   deriving (Eq, Show, Read, Typeable)
@@ -77,6 +89,7 @@
 -- | Internal representation of a spec.
 data SpecTree =
     SpecGroup String [SpecTree]
+  | BuildSpecs (IO [SpecTree])
   | SpecItem String Item
 
 data Item = Item {
@@ -85,11 +98,12 @@
 }
 
 mapSpecItem :: (Item -> Item) -> Spec -> Spec
-mapSpecItem f = fromSpecList . map go . runSpecM
+mapSpecItem f = fromSpecList . return . BuildSpecs . fmap (map go) . runSpecM
   where
     go :: SpecTree -> SpecTree
     go spec = case spec of
       SpecItem r item -> SpecItem r (f item)
+      BuildSpecs es -> BuildSpecs (map go <$> es)
       SpecGroup d es -> SpecGroup d (map go es)
 
 -- | The @describe@ function combines a list of specs into a larger spec.
diff --git a/src/Test/Hspec/Formatters/Internal.hs b/src/Test/Hspec/Formatters/Internal.hs
--- a/src/Test/Hspec/Formatters/Internal.hs
+++ b/src/Test/Hspec/Formatters/Internal.hs
@@ -147,7 +147,7 @@
 
 -- | used to notify the progress of the currently evaluated example
 --
--- NOTE: This is only called when interactive/color mode.
+-- /Note/: This is only called when interactive/color mode.
 , exampleProgress     :: Handle -> Path -> Progress -> IO ()
 
 -- | evaluated after each successful example
diff --git a/src/Test/Hspec/Runner.hs b/src/Test/Hspec/Runner.hs
--- a/src/Test/Hspec/Runner.hs
+++ b/src/Test/Hspec/Runner.hs
@@ -41,23 +41,22 @@
 import           Test.Hspec.Core.QuickCheckUtil
 
 import           Test.Hspec.Options (Options(..), ColorMode(..), defaultOptions)
+import           Test.Hspec.Runner.Tree
 import           Test.Hspec.Runner.Eval
 
 -- | Filter specs by given predicate.
 --
 -- The predicate takes a list of "describe" labels and a "requirement".
-filterSpecs :: (Path -> Bool) -> [SpecTree] -> [SpecTree]
+filterSpecs :: (Path -> Bool) -> [Tree a] -> [Tree a]
 filterSpecs p = goSpecs []
   where
-    goSpecs :: [String] -> [SpecTree] -> [SpecTree]
     goSpecs groups = mapMaybe (goSpec groups)
 
-    goSpec :: [String] -> SpecTree -> Maybe SpecTree
     goSpec groups spec = case spec of
-      SpecItem requirement _ -> guard (p (groups, requirement)) >> return spec
-      SpecGroup group specs -> case goSpecs (groups ++ [group]) specs of
+      Leaf requirement _ -> guard (p (groups, requirement)) >> return spec
+      Node group specs -> case goSpecs (groups ++ [group]) specs of
         [] -> Nothing
-        xs -> Just (SpecGroup group xs)
+        xs -> Just (Node group xs)
 
 -- | Run given spec and write a report to `stdout`.
 -- Exit with `exitFailure` if at least one spec item fails.
@@ -115,10 +114,11 @@
         | otherwise      = spec_
 
   useColor <- doesUseColor h c
+  filteredSpec <- maybe id filterSpecs (configFilterPredicate c) <$> toTree spec
 
   withHiddenCursor useColor h $
     runFormatM useColor (configHtmlOutput c) (configPrintCpuTime c) seed h $ do
-      runFormatter useColor h c formatter (maybe id filterSpecs (configFilterPredicate c) $ runSpecM spec) `finally_` do
+      runFormatter useColor h c formatter filteredSpec `finally_` do
         failedFormatter formatter
 
       footerFormatter formatter
diff --git a/src/Test/Hspec/Runner/Eval.hs b/src/Test/Hspec/Runner/Eval.hs
--- a/src/Test/Hspec/Runner/Eval.hs
+++ b/src/Test/Hspec/Runner/Eval.hs
@@ -10,6 +10,7 @@
 import           Control.Monad.IO.Class (liftIO)
 
 import           Test.Hspec.Util
+import           Test.Hspec.Runner.Tree
 import           Test.Hspec.Core.Type
 import           Test.Hspec.Config
 import           Test.Hspec.Formatters
@@ -17,20 +18,10 @@
 import           Test.Hspec.Timer
 import           Data.Time.Clock.POSIX
 
-data Tree a
-  = Node !String [Tree a]
-  | Leaf !String a
-  deriving (Eq, Show, Functor)
-
-toTree :: SpecTree -> Tree Item
-toTree spec = case spec of
-  SpecGroup label specs -> Node label (map toTree specs)
-  SpecItem r item -> Leaf r item
-
 type EvalTree = Tree (ProgressCallback -> FormatResult -> IO (FormatM ()))
 
 -- | Evaluate all examples of a given spec and produce a report.
-runFormatter :: Bool -> Handle -> Config -> Formatter -> [SpecTree] -> FormatM ()
+runFormatter :: Bool -> Handle -> Config -> Formatter -> [Tree Item] -> FormatM ()
 runFormatter useColor h c formatter specs_ = do
   headerFormatter formatter
   chan <- liftIO newChan
@@ -42,7 +33,7 @@
       | useColor = every 0.05 $ exampleProgress formatter h
       | otherwise = return $ \_ _ -> return ()
 
-    specs = map (fmap (parallelize . fmap (applyNoOpAround . applyParams) . unwrapItem) . toTree) specs_
+    specs = map (fmap (parallelize . fmap (applyNoOpAround . applyParams) . unwrapItem)) specs_
 
     unwrapItem :: Item -> (Bool, Params -> (IO () -> IO ()) -> ProgressCallback -> IO Result)
     unwrapItem (Item isParallelizable e) = (isParallelizable, e)
diff --git a/src/Test/Hspec/Runner/Tree.hs b/src/Test/Hspec/Runner/Tree.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Hspec/Runner/Tree.hs
@@ -0,0 +1,18 @@
+{-# LANGUAGE DeriveFunctor #-}
+module Test.Hspec.Runner.Tree where
+
+import           Control.Applicative
+import           Test.Hspec.Core.Type
+
+data Tree a
+  = Node !String [Tree a]
+  | Leaf !String a
+  deriving (Eq, Show, Functor)
+
+toTree :: Spec -> IO [Tree Item]
+toTree spec = concat <$> (runSpecM spec >>= mapM go)
+  where
+    go x = case x of
+      SpecGroup label xs -> return . Node label . concat <$> mapM go xs
+      BuildSpecs xs -> concat <$> (xs >>= mapM go)
+      SpecItem r item -> return [Leaf r item]
