@@ -42,7 +42,7 @@ import Echidna.Types.Campaign
4242import Echidna.Types.Config
4343import Echidna.Types.Corpus qualified as Corpus
4444import Echidna.Types.Coverage (coverageStats )
45- import Echidna.Types.Test (EchidnaTest (.. ), TestState (.. ), didFail , isOptimizationTest )
45+ import Echidna.Types.Test (EchidnaTest (.. ), TestState (.. ), didFail , isOptimizationTest , needsShrinking )
4646import Echidna.Types.Tx (Tx )
4747import Echidna.Types.Worker
4848import Echidna.UI.Report
@@ -248,6 +248,18 @@ ui vm dict initialCorpus cliSelectedContract = do
248248 , Handler $ \ (e :: SomeException ) -> pure $ Crashed (show e)
249249 ]
250250
251+ -- When a fuzz worker is interrupted by timeout, tests may not have
252+ -- finished shrinking. Run a shrink-only pass outside the timeout using
253+ -- the same worker loop (testLimit=0 means no fuzzing, only shrink).
254+ -- (See github.com/crytic/echidna/issues/839)
255+ case stopReason of
256+ TimeLimitReached | workerType == FuzzWorker -> do
257+ tests <- traverse readIORef env. testRefs
258+ when (any needsShrinking tests) $ void $
259+ runReaderT (runWorker FuzzWorker (get >>= writeIORef stateRef)
260+ vm dict workerId [] 0 cliSelectedContract) env
261+ _ -> pure ()
262+
251263 time <- liftIO getTimestamp
252264 writeChan env. eventQueue (time, WorkerEvent workerId workerType (WorkerStopped stopReason))
253265
0 commit comments