diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,11 @@
 # Release history for stack-all
 
+## 0.3.1 (2021-09-06)
+- newest LTS for a project can now be configured
+- use cached-json-file for snapshots.json
+- add --keep-going
+- exclude lts17 now by default
+
 ## 0.3 (2021-06-26)
 - use hackage.stackage.org/snapshots.json:
   - to determine the latest LTS major version
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -2,13 +2,13 @@
 
 A CLI tool for building Haskell projects easily over Stackage major versions.
 
-This is how I do my Haskell "build ci" now locally.
+This is how I do my Haskell build CI for projects locally with stack.
 
 ## Usage
 
 `stack-all` by default runs `stack build` over
 recent Stackage LTS major versions and Nightly
-(current default is nightly, lts-18, lts-17, lts-16, lts-14,... , lts-11)
+(current default is nightly & LTS 18, 16, 14, 13, 12, 11)
 corresponding to latest major ghc minor versions,
 using the stack's `--resolver` option.
 
@@ -19,24 +19,42 @@
 
 ### Overriding stack.yaml
 `stack-all` can use `stack-ltsXX.yaml` files to override the default `stack.yaml`
-file for particular lts major versions. Note that a `stack-ltsXX.yaml` file
-will also be used for older lts major versions until
+file for particular LTS major versions. Note that a `stack-ltsXX.yaml` file
+will also be used for older LTS major versions until
 another `stack-ltsYY.yaml` file is found.
 
 For example if you have `stack-lts14.yaml` and `stack-lts12.yaml` files
 in your project,
-then `stack.yaml` will be used as normal to build nightly, lts-17 and lts-16,
+then `stack.yaml` will be used as normal to build nightly, lts-18 and lts-16,
 but `stack-lts14.yaml` will be used for building lts-14 and lts-13,
 and `stack-lts12.yaml` will be used for lts-12, lts-11 (and older).
 Since `stack-all` overrides the resolver with `--resolver lts-XX`
-the exact minor lts version specified in the `stack*.yaml` files
+the exact minor LTS version specified in the `stack*.yaml` files
 doesn't actually matter: `stack-all` always uses the latest minor release of
-the lts major version according to Stackage.
+the LTS major version according to Stackage.
 
+`stack-ltsXX.yaml` files can be easily prepared using
+`stack-all --make-lts ltsXX` (or `-s ltsXX` for short).
+
 (Other versioned stack.yaml filenames like stack-ghc-8.8.yaml
 are not currently supported.)
 
-### Configuring the oldest lts to build
+### Specifying LTS versions
+You can abbreviate `lts-XX` args to `ltsXX` on the commandline.
+
+There are `--oldest`  and `--newest` options to specify the range of
+lts versions to build over:
+
+You can specify the oldest major LTS to build for with eg `stack-all -o lts13`.
+Otherwise if not configured the default oldest LTS is currently `lts-11`.
+
+Similarly you can specify the newest LTS version to build from with
+eg `stack-all -n lts16`. (The default is to build from nightly.)
+
+Alternatively, one can give one or more explicit LTS major versions to build for
+as arguments: eg `stack-all lts14` if you only wish to build that version.
+
+### Configuring the oldest and/or newest LTS to build
 You can configure the oldest working LTS major version for your project
 by running for example `stack-all -c -o lts-13` which generates a `.stack-all`
 project config file like this:
@@ -45,28 +63,16 @@
 # lts-12 too old
 oldest = lts-13
 ```
-(the comment line can be used to document why the older lts doesn't work).
-This specifies that the oldest lts version to build for is lts-13.
-
-### Specifying lts versions
-You can abbreviate `lts-XX` args to `ltsXX` on the commandline.
-
-There are `--oldest`  and `--newest` options to specify the range of
-lts versions to build over:
-
-You can specify the oldest major lts to build for with eg `stack-all -o lts13`.
-Otherwise if not configured the default oldest lts is currently `lts-11`.
-
-Similarly you can specify the newest lts version to build from with
-`stack-all -n lts16`. (The default is to build from nightly.)
+(the comment line can be used to document why the older LTS doesn't work).
+This specifies that the oldest LTS version to build for is lts-13.
 
-Alternatively, one can give one or more explicit lts major versions to build for
-as arguments: eg `stack-all lts14` if you only wish to build that version.
+The newest LTS to build with stack-all can similarly be configured:
+`stack-all -c -n lts18` or setting `newest = lts-18`.
 
 ### Running other stack commands
-By default `stack-all` just runs the stack `build` command over lts versions.
+By default `stack-all` just runs the stack `build` command over LTS versions.
 
-You can also specify the stack command to run and its options on the commandline:
+You can also specify a stack command to run with options on the commandline:
 eg
 ```
 $ stack-all test --no-rerun-tests
@@ -78,7 +84,7 @@
 ## Install
 The project is released on Hackage.
 
-You can also build from source with `stack install` or `cabal install`.
+You can also build from git source with `stack install` or `cabal install`.
 
 ## Collaboration
 The project is hosted at https://github.com/juhp/stack-all under a BSD license.
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -19,8 +19,8 @@
 import Paths_stack_all (version)
 import Snapshots
 
-defaultOldest :: MajorVer
-defaultOldest = LTS 11
+defaultOldestLTS :: MajorVer
+defaultOldestLTS = LTS 11
 
 data VersionLimit = DefaultLimit | Oldest MajorVer | AllVersions
 
@@ -34,20 +34,22 @@
     run <$>
     (flagWith' CreateConfig 'c' "create-config" "Create a project .stack-all file" <|>
      flagWith DefaultRun MakeStackLTS 's' "make-lts" "Create a stack-ltsXX.yaml file") <*>
+    switchWith 'k' "keep-going" "Keep going even if an LTS fails" <*>
     switchWith 'd' "debug" "Verbose stack build output on error" <*>
     optional (readMajor <$> strOptionWith 'n' "newest" "MAJOR" "Newest LTS release to build from") <*>
     (Oldest . readMajor <$> strOptionWith 'o' "oldest" "MAJOR" "Oldest compatible LTS release" <|>
      flagWith DefaultLimit AllVersions 'a' "all-lts" "Try to build back to LTS 1 even") <*>
     many (strArg "MAJORVER... [COMMAND...]")
 
-run :: Command -> Bool -> Maybe MajorVer -> VersionLimit -> [String] -> IO ()
-run command debug mnewest verlimit verscmd = do
+run :: Command -> Bool ->Bool -> Maybe MajorVer -> VersionLimit -> [String]
+    -> IO ()
+run command keepgoing debug mnewest verlimit verscmd = do
   findStackProjectDir Nothing
   case command of
     CreateConfig ->
       case verlimit of
-        Oldest oldest -> createStackAll oldest
-        _ -> error' "creating .stack-all requires --oldest LTS"
+        Oldest oldest -> createStackAll (Just oldest) mnewest
+        _ -> createStackAll Nothing mnewest
     MakeStackLTS -> do
       (versions, _) <- getVersionsCmd
       if null versions
@@ -57,7 +59,7 @@
       (versions, cargs) <- getVersionsCmd
       configs <- mapMaybe readStackConf <$> listDirectory "."
       let newestFilter = maybe id (filter . (>=)) mnewest
-      mapM_ (stackBuild configs debug cargs) (newestFilter versions)
+      mapM_ (stackBuild configs keepgoing debug cargs) (newestFilter versions)
   where
     findStackProjectDir :: Maybe FilePath -> IO ()
     findStackProjectDir mcwd = do
@@ -79,7 +81,7 @@
             -- FIXME stack init content too verbose
             unlessM (cmdBool "stack" ["init"]) $
               -- FIXME determine latest stable snapshot automatically
-              writeFile "stack.yaml" "resolver: lts-17.15\n"
+              writeFile "stack.yaml" "resolver: lts-18.8\n"
             else error' "no package/project found"
 
     getVersionsCmd :: IO ([MajorVer],[String])
@@ -91,17 +93,20 @@
         if null verlist then
           case verlimit of
             DefaultLimit -> do
-              oldest <- fromMaybeM (return defaultOldest) readOldestLTS
+              (newestLTS, oldestLTS) <- readNewestOldestLTS
               return $ case mnewest of
                          Just newest ->
-                           if newest < oldest
-                           then filter (newest >=) allMajors
-                           else filter (\ s ->  s >= oldest && newest >= s) allMajors
-                         Nothing -> filter (>= oldest) allMajors
+                           if newest < oldestLTS
+                           then filter (inRange newest (LTS 1)) allMajors
+                           else filter (inRange newest oldestLTS) allMajors
+                         Nothing -> filter (inRange newestLTS oldestLTS) allMajors
             AllVersions -> return allMajors
-            Oldest ver -> return $ filter (>= ver) allMajors
+            Oldest ver -> return $ filter (inRange Nightly ver) allMajors
         else return verlist
       return (versions,if null cmds then ["build"] else cmds)
+      where
+        inRange :: MajorVer -> MajorVer -> MajorVer -> Bool
+        inRange newest oldest v = v >= oldest && v <= newest
 
 readStackConf :: FilePath -> Maybe MajorVer
 readStackConf "stack-lts.yaml" = error' "unversioned stack-lts.yaml is unsupported"
@@ -111,34 +116,44 @@
 stackAllFile :: FilePath
 stackAllFile = ".stack-all"
 
-createStackAll :: MajorVer -> IO ()
-createStackAll ver = do
+createStackAll :: Maybe MajorVer -> Maybe MajorVer -> IO ()
+createStackAll Nothing Nothing =
+  error' "creating .stack-all requires --oldest LTS and/or --newest LTS"
+createStackAll moldest mnewest = do
   exists <- doesFileExist stackAllFile
   if exists then error' $ stackAllFile ++ " already exists"
     else do
     allMajors <- getMajorVers
-    let older =
-          let molder = listToMaybe $ dropWhile (>= ver) allMajors
-          in maybe "" (\s -> showMajor s ++ " too old") molder
     writeFile stackAllFile $
-      "[versions]\n# " ++ older ++ "\noldest = " ++ showMajor ver ++ "\n"
+      "[versions]\n" ++
+      case mnewest of
+        Nothing -> ""
+        Just newest ->
+          "newest = " ++ showMajor newest ++ "\n"
+      ++
+      case moldest of
+        Nothing -> ""
+        Just oldest ->
+          let older =
+                let molder = listToMaybe $ dropWhile (>= oldest) allMajors
+                in maybe "" (\s -> showMajor s ++ " too old") molder
+          in "# " ++ older ++ "\noldest = " ++ showMajor oldest ++ "\n"
 
-readOldestLTS :: IO (Maybe MajorVer)
-readOldestLTS = do
+readNewestOldestLTS :: IO (MajorVer,MajorVer)
+readNewestOldestLTS = do
   haveConfig <- doesFileExist stackAllFile
   if haveConfig then
-    Just . readMajor <$> readIniConfig stackAllFile rcParser id
-    else return Nothing
+    readIniConfig stackAllFile $
+    section "versions" $ do
+    mnewest <- fmap readMajor <$> fieldMbOf "newest" string
+    moldest <- fmap readMajor <$> fieldMbOf "oldest" string
+    return (fromMaybe Nightly mnewest, fromMaybe defaultOldestLTS moldest)
+    else return (Nightly, defaultOldestLTS)
   where
-    rcParser :: IniParser String
-    rcParser =
-      section "versions" $
-      fieldOf "oldest" string
-
-    readIniConfig :: FilePath -> IniParser a -> (a -> b) -> IO b
-    readIniConfig inifile iniparser fn = do
+    readIniConfig :: FilePath -> IniParser a -> IO a
+    readIniConfig inifile iniparser = do
       ini <- T.readFile inifile
-      return $ either error fn $ parseIniFile ini iniparser
+      return $ either error id $ parseIniFile ini iniparser
 
 makeStackLTS :: [MajorVer] -> IO ()
 makeStackLTS vers = do
@@ -162,8 +177,8 @@
     compactMajor Nightly = "nightly"
     compactMajor (LTS ver) = "lts" ++ show ver
 
-stackBuild :: [MajorVer] -> Bool -> [String] -> MajorVer -> IO ()
-stackBuild configs debug command ver = do
+stackBuild :: [MajorVer] -> Bool -> Bool -> [String] -> MajorVer -> IO ()
+stackBuild configs keepgoing debug command ver = do
   let config =
         case sort (filter (ver <=) configs) of
           [] -> []
@@ -178,7 +193,7 @@
         then debugBuild $ opts ++ command
         else do
         ok <- cmdBool "stack" $ opts ++ command
-        unless ok $ do
+        unless (ok || keepgoing) $ do
           putStr "\nsnapshot-pkg-db: "
           cmd_ "stack" $ "--silent" : opts ++ ["path", "--snapshot-pkg-db"]
           error' $ "failed for " ++ showMajor ver
diff --git a/src/MajorVer.hs b/src/MajorVer.hs
--- a/src/MajorVer.hs
+++ b/src/MajorVer.hs
@@ -34,7 +34,7 @@
   case maybeReadMajor ver of
     Just s -> s
     Nothing ->
-      error' $ "couldn't parse " ++ ver ++ " (expected lts-XX or ltsXX)"
+      error' $! "couldn't parse " ++ ver ++ " (expected lts-XX or ltsXX)"
 
 -- readCompactMajor "lts16"
 readCompactMajor :: String -> Maybe MajorVer
@@ -43,7 +43,7 @@
   if "lts" `isPrefixOf` ver then
     case readMaybe (dropPrefix "lts" ver) of
       Just major -> Just (LTS major)
-      Nothing -> error' $ "couldn't parse compact " ++ ver ++  " (expected ltsXX)"
+      Nothing -> error' $! "couldn't parse compact " ++ ver ++  " (expected ltsXX)"
   else Nothing
 
 eitherReadMajor :: String -> Either String MajorVer
diff --git a/src/Snapshots.hs b/src/Snapshots.hs
--- a/src/Snapshots.hs
+++ b/src/Snapshots.hs
@@ -6,23 +6,17 @@
   )
 where
 
-import Control.Monad
 import Data.Aeson
-import qualified Data.ByteString.Lazy.Char8 as B
 import Data.List.Extra
 import qualified Data.HashMap.Strict as H
 import qualified Data.Text as T
-import Data.Time.Clock (diffUTCTime, getCurrentTime)
 import Network.HTTP.Query
-import SimpleCmd (error')
-import System.Directory
-import System.Environment.XDG.BaseDir
-import System.FilePath
+import System.Cached.JSON
 
 import MajorVer
 
 excludedMajors :: [MajorVer]
-excludedMajors = [LTS 15, LTS 7, LTS 3, LTS 0]
+excludedMajors = [LTS 17, LTS 15, LTS 7, LTS 3, LTS 0]
 
 getMajorVers :: IO [MajorVer]
 getMajorVers = do
@@ -33,28 +27,6 @@
 latestSnapshot ver = do
   lookupKey (T.pack (showMajor ver)) <$> getSnapshots
 
--- FIXME handle network failure
 getSnapshots :: IO Object
-getSnapshots = do
-  file <- getUserCacheFile "stack-all" "snapshots.json"
-  exists <- doesFileExist file
-  unless exists $ do
-    putStrLn $ "Creating " ++ file ++ " ..."
-    createDirectoryIfMissing True (takeDirectory file)
-  recent <- do
-    if exists then do
-      ts <- getModificationTime file
-      t <- getCurrentTime
-      -- < 3 hours
-      return $ diffUTCTime t ts < 10000
-      else return False
-  if recent
-    then do
-    eobj <- eitherDecode <$> B.readFile file
-    case eobj of
-      Left err -> error' err
-      Right obj -> return obj
-    else do
-    obj <- webAPIQuery "http://haddock.stackage.org/snapshots.json" []
-    B.writeFile file $ encode obj
-    return obj
+getSnapshots =
+  getCachedJSON "stackage-snapshots" "snapshots.json" "http://haddock.stackage.org/snapshots.json" 200
diff --git a/stack-all.cabal b/stack-all.cabal
--- a/stack-all.cabal
+++ b/stack-all.cabal
@@ -1,5 +1,5 @@
 name:                stack-all
-version:             0.3
+version:             0.3.1
 synopsis:            CLI tool for building across Stackage major versions
 description:
         Build your Haskell project over Stackage major LTS versions.
@@ -16,7 +16,7 @@
                      ChangeLog.md
 cabal-version:       1.18
 tested-with:         GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.5,
-                     GHC == 8.8.4, GHC == 8.10.4, GHC == 9.0.1
+                     GHC == 8.8.4, GHC == 8.10.5, GHC == 9.0.1
 
 source-repository head
   type:                git
@@ -30,7 +30,7 @@
   hs-source-dirs:      src
   build-depends:       base < 5,
                        aeson,
-                       bytestring,
+                       cached-json-file,
                        config-ini,
                        directory >= 1.2.5,
                        extra >= 1.6.15,
@@ -40,9 +40,7 @@
                        simple-cmd >= 0.1.4,
                        simple-cmd-args,
                        text,
-                       time,
-                       unordered-containers,
-                       xdg-basedir
+                       unordered-containers
   default-language:    Haskell2010
   ghc-options:         -Wall
   if impl(ghc >= 8.0)
