diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,6 +1,9 @@
 # Changelog for MIP
 
-## Unreleased changes
+## 0.2.0.1 (2026-01-05)
+
+* Add solution files needed for testing as `extra-source-files`
+* Produce more user-friendly error messages when the CBC solver exited with 0 but the output file was not generated (thanks to @dpvanbalen)
 
 ## 0.2.0.0 (2025-02-03)
 
diff --git a/MIP.cabal b/MIP.cabal
--- a/MIP.cabal
+++ b/MIP.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           MIP
-version:        0.2.0.0
+version:        0.2.0.1
 synopsis:       Library for using Mixed Integer Programming (MIP)
 description:    Please see the README on GitHub at <https://github.com/msakai/haskell-MIP/tree/master/MIP#readme>
 category:       Math, Algorithms, Optimisation, Optimization
@@ -63,6 +63,22 @@
     samples/lp/test-user-cuts.lp
     samples/lp/test.lp
     samples/lp/unbounded-ip.lp
+    samples/lp/test-solution-cbc-infeasible.txt
+    samples/lp/test-solution-cbc-unbounded.txt
+    samples/lp/test-solution-cbc.txt
+    samples/lp/test-solution-cplex-unbounded.sol
+    samples/lp/test-solution-cplex.sol
+    samples/lp/test-solution-glpk-infeasible.sol
+    samples/lp/test-solution-glpk-long.sol
+    samples/lp/test-solution-glpk-unbounded.sol
+    samples/lp/test-solution-glpk.sol
+    samples/lp/test-solution-gurobi.sol
+    samples/lp/test-solution-highs-infeasible.sol
+    samples/lp/test-solution-highs-unbounded.sol
+    samples/lp/test-solution-highs.sol
+    samples/lp/test-solution-scip.sol
+    samples/lp/test-solution-printemps-infeasible.json
+    samples/lp/test-solution-printemps.json
 extra-doc-files:
     doc-images/MIP-Status-diagram.png
 
@@ -153,6 +169,7 @@
     , base >=4.12 && <5
     , containers >=0.5.0
     , data-default-class
+    , directory >=1.3.3.0 && <1.4.0.0
     , extended-reals >=0.1 && <1.0
     , filepath
     , hashable >=1.2.7.0 && <1.6.0.0
diff --git a/samples/lp/test-solution-cbc-infeasible.txt b/samples/lp/test-solution-cbc-infeasible.txt
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-cbc-infeasible.txt
@@ -0,0 +1,4 @@
+Integer infeasible - objective value 0.00000000
+      0 x             0.11111111                       0
+      1 y                      0                       0
+      2 z             0.33333333                       0
diff --git a/samples/lp/test-solution-cbc-unbounded.txt b/samples/lp/test-solution-cbc-unbounded.txt
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-cbc-unbounded.txt
@@ -0,0 +1,3 @@
+Unbounded - objective value 0.00000000
+**       0 x                      0                       0
+      1 y                      0                    -1.5
diff --git a/samples/lp/test-solution-cbc.txt b/samples/lp/test-solution-cbc.txt
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-cbc.txt
@@ -0,0 +1,5 @@
+Optimal - objective value -122.50000000
+      0 x1                    40                      -4
+      1 x2                  10.5                       0
+      2 x3                  19.5                       0
+      3 x4                     3                    32.5
diff --git a/samples/lp/test-solution-cplex-unbounded.sol b/samples/lp/test-solution-cplex-unbounded.sol
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-cplex-unbounded.sol
@@ -0,0 +1,32 @@
+<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>
+<CPLEXSolution version="1.2">
+ <header
+   problemName="samples/lp/test-solution-glpk-unbounded.lp"
+   solutionName="incumbent"
+   solutionIndex="-1"
+   objectiveValue="3"
+   solutionTypeValue="3"
+   solutionTypeString="primal"
+   solutionStatusValue="118"
+   solutionStatusString="integer unbounded"
+   solutionMethodString="mip"
+   primalFeasible="1"
+   dualFeasible="0"
+   MIPNodes="0"
+   MIPIterations="0"
+   writeLevel="1"/>
+ <quality
+   epInt="1.0000000000000001e-05"
+   epRHS="9.9999999999999995e-07"
+   maxIntInfeas="0"
+   maxPrimalInfeas="0"
+   maxX="2"
+   maxSlack="0"/>
+ <linearConstraints>
+  <constraint name="c1" index="0" slack="0"/>
+ </linearConstraints>
+ <variables>
+  <variable name="x" index="0" value="1"/>
+  <variable name="y" index="1" value="2"/>
+ </variables>
+</CPLEXSolution>
diff --git a/samples/lp/test-solution-cplex.sol b/samples/lp/test-solution-cplex.sol
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-cplex.sol
@@ -0,0 +1,36 @@
+<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>
+<CPLEXSolution version="1.2">
+ <header
+   problemName="samples/lp/test.lp"
+   solutionName="incumbent"
+   solutionIndex="-1"
+   objectiveValue="122.5"
+   solutionTypeValue="3"
+   solutionTypeString="primal"
+   solutionStatusValue="101"
+   solutionStatusString="integer optimal solution"
+   solutionMethodString="mip"
+   primalFeasible="1"
+   dualFeasible="1"
+   MIPNodes="0"
+   MIPIterations="3"
+   writeLevel="1"/>
+ <quality
+   epInt="1.0000000000000001e-05"
+   epRHS="9.9999999999999995e-07"
+   maxIntInfeas="0"
+   maxPrimalInfeas="0"
+   maxX="40"
+   maxSlack="2"/>
+ <linearConstraints>
+  <constraint name="c1" index="0" slack="0"/>
+  <constraint name="c2" index="1" slack="2"/>
+  <constraint name="c3" index="2" slack="0"/>
+ </linearConstraints>
+ <variables>
+  <variable name="x1" index="0" value="40"/>
+  <variable name="x2" index="1" value="10.5"/>
+  <variable name="x3" index="2" value="19.5"/>
+  <variable name="x4" index="3" value="3"/>
+ </variables>
+</CPLEXSolution>
diff --git a/samples/lp/test-solution-glpk-infeasible.sol b/samples/lp/test-solution-glpk-infeasible.sol
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-glpk-infeasible.sol
@@ -0,0 +1,30 @@
+Problem:    
+Rows:       3
+Columns:    3 (3 integer, 0 binary)
+Non-zeros:  5
+Status:     INTEGER EMPTY
+Objective:  obj = 0 (MINimum)
+
+   No.   Row name        Activity     Lower bound   Upper bound
+------ ------------    ------------- ------------- -------------
+     1 r.4                         0             0             = 
+     2 r.5                         0             1               
+     3 r.6                         0                           2 
+
+   No. Column name       Activity     Lower bound   Upper bound
+------ ------------    ------------- ------------- -------------
+     1 x            *              0                             
+     2 y            *              0                             
+     3 z            *              0             0               
+
+Integer feasibility conditions:
+
+KKT.PE: max.abs.err = 0.00e+00 on row 0
+        max.rel.err = 0.00e+00 on row 0
+        High quality
+
+KKT.PB: max.abs.err = 1.00e+00 on row 2
+        max.rel.err = 5.00e-01 on row 2
+        SOLUTION IS INFEASIBLE
+
+End of output
diff --git a/samples/lp/test-solution-glpk-long.sol b/samples/lp/test-solution-glpk-long.sol
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-glpk-long.sol
@@ -0,0 +1,32 @@
+Problem:    
+Rows:       3
+Columns:    4 (1 integer, 0 binary)
+Non-zeros:  9
+Status:     INTEGER OPTIMAL
+Objective:  obj = 122.5 (MAXimum)
+
+   No.   Row name        Activity     Lower bound   Upper bound
+------ ------------    ------------- ------------- -------------
+     1 c1                         20                          20 
+     2 c2                         28                          30 
+     3 c3                          0             0             = 
+
+   No. Column name       Activity     Lower bound   Upper bound
+------ ------------    ------------- ------------- -------------
+     1 x1AAAAAAAAAAAAAAAAAAAAAAAAAAAA
+                                  40             0            40 
+     2 x2                       10.5             0               
+     3 x3                       19.5             0               
+     4 x4           *              3             2             3 
+
+Integer feasibility conditions:
+
+KKT.PE: max.abs.err = 0.00e+00 on row 0
+        max.rel.err = 0.00e+00 on row 0
+        High quality
+
+KKT.PB: max.abs.err = 0.00e+00 on row 0
+        max.rel.err = 0.00e+00 on row 0
+        High quality
+
+End of output
diff --git a/samples/lp/test-solution-glpk-unbounded.sol b/samples/lp/test-solution-glpk-unbounded.sol
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-glpk-unbounded.sol
@@ -0,0 +1,27 @@
+Problem:    
+Rows:       1
+Columns:    2 (2 integer, 0 binary)
+Non-zeros:  2
+Status:     INTEGER UNDEFINED
+Objective:  obj = 0 (MAXimum)
+
+   No.   Row name        Activity     Lower bound   Upper bound
+------ ------------    ------------- ------------- -------------
+     1 c1                          0             0               
+
+   No. Column name       Activity     Lower bound   Upper bound
+------ ------------    ------------- ------------- -------------
+     1 x            *              0             1               
+     2 y            *              0             0               
+
+Integer feasibility conditions:
+
+KKT.PE: max.abs.err = 0.00e+00 on row 0
+        max.rel.err = 0.00e+00 on row 0
+        High quality
+
+KKT.PB: max.abs.err = 1.00e+00 on column 1
+        max.rel.err = 5.00e-01 on column 1
+        SOLUTION IS INFEASIBLE
+
+End of output
diff --git a/samples/lp/test-solution-glpk.sol b/samples/lp/test-solution-glpk.sol
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-glpk.sol
@@ -0,0 +1,31 @@
+Problem:    
+Rows:       3
+Columns:    4 (1 integer, 0 binary)
+Non-zeros:  9
+Status:     INTEGER OPTIMAL
+Objective:  obj = 122.5 (MAXimum)
+
+   No.   Row name        Activity     Lower bound   Upper bound
+------ ------------    ------------- ------------- -------------
+     1 c1                         20                          20 
+     2 c2                         28                          30 
+     3 c3                          0             0             = 
+
+   No. Column name       Activity     Lower bound   Upper bound
+------ ------------    ------------- ------------- -------------
+     1 x1                         40             0            40 
+     2 x2                       10.5             0               
+     3 x3                       19.5             0               
+     4 x4           *              3             2             3 
+
+Integer feasibility conditions:
+
+KKT.PE: max.abs.err = 0.00e+00 on row 0
+        max.rel.err = 0.00e+00 on row 0
+        High quality
+
+KKT.PB: max.abs.err = 0.00e+00 on row 0
+        max.rel.err = 0.00e+00 on row 0
+        High quality
+
+End of output
diff --git a/samples/lp/test-solution-gurobi.sol b/samples/lp/test-solution-gurobi.sol
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-gurobi.sol
@@ -0,0 +1,6 @@
+# Solution for model obj
+# Objective value = 1.2250000000000006e+02
+x1 40
+x2 10.5
+x3 1.9500000000000018e+01
+x4 3
diff --git a/samples/lp/test-solution-highs-infeasible.sol b/samples/lp/test-solution-highs-infeasible.sol
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-highs-infeasible.sol
@@ -0,0 +1,12 @@
+Model status
+Infeasible
+
+# Primal solution values
+None
+
+# Dual solution values
+None
+
+# Basis
+HiGHS v1
+None
diff --git a/samples/lp/test-solution-highs-unbounded.sol b/samples/lp/test-solution-highs-unbounded.sol
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-highs-unbounded.sol
@@ -0,0 +1,12 @@
+Model status
+Primal infeasible or unbounded
+
+# Primal solution values
+None
+
+# Dual solution values
+None
+
+# Basis
+HiGHS v1
+None
diff --git a/samples/lp/test-solution-highs.sol b/samples/lp/test-solution-highs.sol
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-highs.sol
@@ -0,0 +1,22 @@
+Model status
+Optimal
+
+# Primal solution values
+Feasible
+Objective 122.5
+# Columns 4
+x1 40
+x2 10.5
+x3 19.5
+x4 3
+# Rows 3
+c1 20
+c2 28
+c3 0
+
+# Dual solution values
+None
+
+# Basis
+HiGHS v1
+None
diff --git a/samples/lp/test-solution-printemps-infeasible.json b/samples/lp/test-solution-printemps-infeasible.json
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-printemps-infeasible.json
@@ -0,0 +1,22 @@
+{ 
+    "version": "v2.5.0", 
+    "name": "infeasible2", 
+    "number_of_variables": 2, 
+    "number_of_constraints": 2, 
+    "is_found_feasible_solution": false, 
+    "objective": 0.000000e+00, 
+    "total_violation": 2.000000e+00, 
+    "variables": { 
+        "x": 0, 
+        "y": 0
+    }, 
+    "expressions": {}, 
+    "constraints": { 
+        "row1": 1.000000e+00, 
+        "row2": 1.000000e+00
+    }, 
+    "violations": { 
+        "row1": 1.000000e+00, 
+        "row2": 1.000000e+00
+    }
+}
diff --git a/samples/lp/test-solution-printemps.json b/samples/lp/test-solution-printemps.json
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-printemps.json
@@ -0,0 +1,30 @@
+{ 
+    "version": "v2.5.0", 
+    "name": "test", 
+    "number_of_variables": 4, 
+    "number_of_constraints": 4, 
+    "is_found_feasible_solution": true, 
+    "objective": -1.070000e+02, 
+    "total_violation": 0.000000e+00, 
+    "variables": { 
+        "x3": 22, 
+        "x2": 7, 
+        "x1": 29, 
+        "x4": 2
+    }, 
+    "expressions": { 
+        "x2_dependent": 7.000000e+00
+    }, 
+    "constraints": { 
+        "c3_less": -3.000000e+00, 
+        "c1": 0.000000e+00, 
+        "c2": 0.000000e+00, 
+        "c3": 0.000000e+00
+    }, 
+    "violations": { 
+        "c3_less": 0.000000e+00, 
+        "c1": 0.000000e+00, 
+        "c2": 0.000000e+00, 
+        "c3": 0.000000e+00
+    }
+}
diff --git a/samples/lp/test-solution-scip.sol b/samples/lp/test-solution-scip.sol
new file mode 100644
--- /dev/null
+++ b/samples/lp/test-solution-scip.sol
@@ -0,0 +1,6 @@
+solution status: optimal solution found
+objective value:                                122.5
+x4                                                  3 	(obj:1)
+x2                                               10.5 	(obj:2)
+x3                                               19.5 	(obj:3)
+x1                                                 40 	(obj:1)
diff --git a/src/Numeric/Optimization/MIP/Solver/CBC.hs b/src/Numeric/Optimization/MIP/Solver/CBC.hs
--- a/src/Numeric/Optimization/MIP/Solver/CBC.hs
+++ b/src/Numeric/Optimization/MIP/Solver/CBC.hs
@@ -17,10 +17,12 @@
   , cbc
   ) where
 
+import Control.Monad
 import Data.Default.Class
 import qualified Data.Text.Lazy.IO as TLIO
+import System.Directory
 import System.Exit
-import System.IO
+import System.FilePath
 import System.IO.Temp
 import qualified Numeric.Optimization.MIP.Base as MIP
 import qualified Numeric.Optimization.MIP.LPFile as LPFile
@@ -49,35 +51,36 @@
     case LPFile.render def prob{ MIP.objectiveFunction = obj' } of
       Left err -> ioError $ userError err
       Right lp -> do
-        withSystemTempFile "cbc.lp" $ \fname1 h1 -> do
-          TLIO.hPutStr h1 lp
-          hClose h1
-          withSystemTempFile "cbc.sol" $ \fname2 h2 -> do
-            hClose h2
-            let args = cbcArgs solver
-                    ++ [fname1]
-                    ++ (case solveTimeLimit opt of
-                          Nothing -> []
-                          Just sec -> ["sec", show sec])
-                    ++ (case solveTol opt of
-                          Nothing -> []
-                          Just tol ->
-                            [ "integerTolerance", show (MIP.integralityTol tol)
-                            , "primalTolerance", show (MIP.feasibilityTol tol)
-                            , "dualTolerance", show (MIP.optimalityTol tol)
-                            ])
-                    ++ ["solve", "solu", fname2]
-                onGetLine = solveLogger opt
-                onGetErrorLine = solveErrorLogger opt
-            exitcode <- runProcessWithOutputCallback (cbcPath solver) args Nothing "" onGetLine onGetErrorLine
-            case exitcode of
-              ExitFailure n -> ioError $ userError $ "exit with " ++ show n
-              ExitSuccess -> do
-                sol <- CBCSol.readFile fname2
-                if isMax then
-                  return $ sol{ MIP.solObjectiveValue = fmap negate (MIP.solObjectiveValue sol) }
-                else
-                  return sol
+        withSystemTempDirectory "haskell-mip-cbc" $ \dir -> do
+          let fname1 = dir </> "cbc.lp"
+              fname2 = dir </> "cbc.sol"
+          TLIO.writeFile fname1 lp
+          let args = cbcArgs solver
+                  ++ [fname1]
+                  ++ (case solveTimeLimit opt of
+                        Nothing -> []
+                        Just sec -> ["sec", show sec])
+                  ++ (case solveTol opt of
+                        Nothing -> []
+                        Just tol ->
+                          [ "integerTolerance", show (MIP.integralityTol tol)
+                          , "primalTolerance", show (MIP.feasibilityTol tol)
+                          , "dualTolerance", show (MIP.optimalityTol tol)
+                          ])
+                  ++ ["solve", "solu", fname2]
+              onGetLine = solveLogger opt
+              onGetErrorLine = solveErrorLogger opt
+          exitcode <- runProcessWithOutputCallback (cbcPath solver) args Nothing "" onGetLine onGetErrorLine
+          case exitcode of
+            ExitFailure n -> ioError $ userError $ "exit with " ++ show n
+            ExitSuccess -> do
+              m <- doesFileExist fname2
+              unless m $ ioError $ userError "CBC returned exit code 0, but wrote no solution file. You may want to use the solveLogger or solveErrorLogger for more information"
+              sol <- CBCSol.readFile fname2
+              if isMax then
+                return $ sol{ MIP.solObjectiveValue = fmap negate (MIP.solObjectiveValue sol) }
+              else
+                return sol
     where
       obj = MIP.objectiveFunction prob
       isMax = MIP.objDir obj == MIP.OptMax
diff --git a/test/Test/MIPSolver.hs b/test/Test/MIPSolver.hs
--- a/test/Test/MIPSolver.hs
+++ b/test/Test/MIPSolver.hs
@@ -1,10 +1,12 @@
 {-# OPTIONS_GHC -Wall -Wno-unused-top-binds #-}
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TemplateHaskell #-}
 module Test.MIPSolver (mipSolverTestGroup) where
 
 import Control.Arrow ((***))
+import Control.Exception
 import Control.Monad
 import Data.Default.Class
 import qualified Data.Map as Map
@@ -56,6 +58,20 @@
       , " but got: " ++ show status
       ]
 
+case_cbc_no_output_file :: Assertion
+case_cbc_no_output_file = do
+  let prob = def
+       { MIP.constraints =
+           [ MIP.varExpr "x" - MIP.varExpr "x" MIP..<=. 0
+           ]
+       , MIP.varDomains = Map.fromList [("x", (MIP.ContinuousVariable, (0, 1)))]
+       }
+  ret <- try $ solve cbc def prob
+  let expected = userError "CBC returned exit code 0, but wrote no solution file. You may want to use the solveLogger or solveErrorLogger for more information"
+  case ret of
+    Left (e :: IOException) -> e @?= expected
+    Right _ -> assertFailure "An exception should have been raised"
+
 -- ------------------------------------------------------------------------
 
 case_cplex :: Assertion
@@ -318,6 +334,7 @@
   , testCase "cbc unbounded" case_cbc_unbounded
   , testCase "cbc infeasible" case_cbc_infeasible
   , testCase "cbc infeasible2" case_cbc_infeasible2
+  , testCase "cbc no output file" case_cbc_no_output_file
   ]
 #endif
 #ifdef TEST_CPLEX
