# Help needed narrowing down an IO exception

**URL:** <https://discourse.haskell.org/t/help-needed-narrowing-down-an-io-exception/4698>\
**Category:** Learn\
**Created:** [June 23, 2022, 8:17pm UTC](https://discourse.haskell.org/t/help-needed-narrowing-down-an-io-exception/4698 "2022-06-23T20:17:54Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![rubenmoor](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/rubenmoor/32/2075_2.png) [@rubenmoor](https://discourse.haskell.org/u/rubenmoor)\
**Post date:** [June 23, 2022, 8:17pm UTC](https://discourse.haskell.org/t/help-needed-narrowing-down-an-io-exception/4698/1 "2022-06-23T20:17:54Z")

</div>

I am using [obelisk](https://github.com/obsidiansystems/obelisk) with servant and servant-snap. I implemented a custom authentication scheme and testing it I encountered a weird error:

```haskell
A web handler threw an exception. Details: no value

```

This means: Accessing a backend route (handled by snap), there was an HTTP 500 status code and the error message is quite generic. As far as I understand, snap tries to tell me that there was an unhandled IO exception in the handler. (which I can confirm, because I can catch this IO exception)

Specifically “no value” implies that I am running `evalSnap` on a snap handler (in my case: `authHandler :: Snap UserInfo`) that at some point calls `finishWith :: Response -> Snap a`, which is non-sensical. Only that I never explicitly call `finishWith`. So this can’t be it.

I then basically sprinkled my code with `putStrLn $ "Hi, there!" <> -- ...` to figure out what line causes the problem. And I could narrow down the offender to this piece of code:

```haskell
verifyCompactJWT
    :: forall m. (MonadIO m, MonadError JWTError m, MonadBaseControl IO m) => JWK -> CompactJWT -> UTCTime -> m Text
verifyCompactJWT jwk (CompactJWT str) now = do
    liftIO $ putStrLn "In verifyCompactJWT"
    jwt <- decodeCompact $ BL.fromStrict $ Text.encodeUtf8 str
    liftIO $ putStrLn $ "In verifyCompactJWT: jwt: " <> Text.take 16 (Text.pack (show jwt))
    let config = defaultJWTValidationSettings (== audience)
    claims <- fromJust <$> handle handler (Just <$> verifyClaimsAt config jwk now jwt)
    liftIO $ putStrLn $ "In verifyCompactJWT: claims: " <> Text.pack (show claims)
    case claims ^. claimSub ^? _Just . string of
        Nothing -> do
          throwError $ JWTClaimsSetDecodeError "no subject in claims"
        Just s -> do
          liftIO $ putStrLn $ "verifyCompactJWT: sub: " <> s
          pure s
  where
    handler :: SomeException -> m (Maybe ClaimsSet)
    handler e = do
      liftIO $ putStrLn $ "Caught exception in verifyCompactJWT: " <> Text.pack (show e)
      pure Nothing

```

`verifyCompactJWT` is a function called by my handler and this is the output I get:

```haskell
In verifyCompactJWT
In verifyCompactJWT: jwt: JWS Base64Octets
[23/Jun/2022:15:13:23 -0500] During processing of request from 127.0.0.1:56048
request:
"GET /api/user/app/get HTTP/1.1\naccept-language: en-US,en;q=0.9\nreferer: http://localhost:8000/\nsec-fetch-dest: empty\nsec-fetch-mode: cors\nsec-fetch-site: same-origin\naccept: */*\nsec-ch-ua-platform: \"Linux\"\nx-alias: rubenmoor\nuser-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36\nsec-ch-ua-mobile: ?0\nauthorization: Bearer eyJhbGciOiJFUzM4NCJ9.eyJzdWIiOiJydWJlbm1vb3IiLCJleHAiOjEuNjU1OTQzNjM4NTY0Mzg1Mjc5ZTksImlhdCI6MS42NTU5NDM2MDg1NjQzODUyNzllOSwiYXVkIjoiaHR0cHM6Ly9wYWxhbnR5cGUuY29tIn0.td1jCKoE5QSciU2gUde3FqFrCljSCQzJUwTS09EvfA9K0YY6YZrlfxZakcwCTPLo7c8jI3c5xI32maQok5lQUp50WUcxXz_k1NNeeDmuQfPq0P34HmEJBmcH7S-Dog8G\nsec-ch-ua: \" Not A;Brand\";v=\"99\", \"Chromium\";v=\"102\", \"Google Chrome\";v=\"102\"\nconnection: keep-alive\nhost: localhost:8000\nx-real-ip: 127.0.0.1\naccept-encoding: gzip\n\nsn=\"localhost:8000\" c=127.0.0.1:56048 s=127.0.0.1:58115 ctx=/ clen=n/a"
A web handler threw an exception. Details:
no value
127.0.0.1 - - [23/Jun/2022:15:13:23 -0500] "GET /api/user/app/get HTTP/1.1" 500 51 "http://localhost:8000/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36"

```

Right where the proper code execution stops, I tried to catch an error. But in vain. The error isn’t caught, the lines thereafter are never reached and the handler fails.

My code behaves as if `verifyClaimsAt` called `finishWith`, only that `verifyClaimsAt` isn’t aware of `Snap` or `MonadSnap` and thus can’t do that.

How do I go about nailing down this error?

---

<div class="post-metadata">

**Author:** ![rubenmoor](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/rubenmoor/32/2075_2.png) [@rubenmoor](https://discourse.haskell.org/u/rubenmoor)\
**Post date:** [June 23, 2022, 10:11pm UTC](https://discourse.haskell.org/t/help-needed-narrowing-down-an-io-exception/4698/2 "2022-06-23T22:11:11Z")

</div>

I found the cause of my demise:

`verifyClaimsAt` indeed fails to verify, because I set the expiry to 30 seconds instead of 30 days. So far so good, otherwise I would have noticed the problem in 30 days.

Jumping out of the function `verifyCompactJWT`, it gets evaluated via `runExceptT` to `Left JWTExpired`. This I never see because I then move on to call

`throwError :: MonadSnap m => ServantError -> m a`

… which apparently cannot be used safely with `evalSnap` (it seems to work fine in the normal case, which is `runSnap`). In my case `throwError $ err500 { errBody = "JWTExpired"` just results in Snap’s malfunction `throwIO $ ErrorCall "no value"`. Bad luck I guess.

Ironically, I recall how making mistakes in error handling code in JavaScript is hellish. The same is true for Haskell: getting IO exception while gullibly implementing pure error handling is quite confusing.

I am currently stuck with snap-core-1.0.4.1 and snap-server-1.1.1.2. I wonder whether this particular behavior (`throwError` causing IO exceptions in `evalSnap`) is tolerated or an actual bug. I will post an issue there.
