Custom JupyterHub Error Messages

Hi,

I would like to render a custom error depending on the return status of a command call in a pre-spawn hook.

Does anyone have experience with something like this or similar? It seems to me like I should be using something like what’s in handlers/pages.py but I’m not entirely sure.

Thanks!

Hi @kellyrowland! Welcome :slight_smile:

I tried this in a jupyterhub_config.py file:

from tornado import web

def pre_spawn_hook(spawner):
    raise web.HTTPError(403)

c.Spawner.pre_spawn_hook = pre_spawn_hook

This raised a 403 error when the user tried to start their server. Is this what you are looking for? You can use any of the HTTP error codes here.

Do you need to style that page too? I’m not quite sure exactly how to do that yet, but I’m guessing you can do so by modifying the Jinja templates for those errors. https://jupyterhub.readthedocs.io/en/stable/reference/templates.html has some pointers on how to do that, although I haven’t tried to do it myself.

Hope this is useful!

Thanks for your quick response @yuvipanda !

That is pretty close to what I am looking for, but I would like to include some custom error text as well (I made a bit of progress since posting, so I hadn’t gotten to this point then).

Some light Googling has suggested that I define a small basic handler class and set the message in there - I plan to try that Monday morning.

2 Likes

Awesome. Do post here when you have something working :slight_smile:

If it’s just a little text, @yuvipanda’s proposal should work, but in testing I found a bug where errors raised in pre_spawn_hook can end up wrapped in a HTTPError(500) (not always), so it’s hard to get access to what you need here without some changes in JupyterHub. We should have an Issue about letting HTTPErrors propagate up here.

For the template, if you create a directory templates with a single file in it: templates/error.html:

{% extends "templates/error.html" %}

{% block error_detail %}
{% if exception and exception.my_message %}
<div class="error">
<p>Custom exception: {{ exception.my_message }}</p>
</div>
{% else %}
{{ super() }}
{% endif %}

{% endblock error_detail %}

This template will show a custom message if there is one:

The else...super() bit means do what the original template would have done if your .my_message is not defined.

and then have in jupyterhub_config.py, register your templates and a hook to raise the error for testing:


# tell jupyterhub to find your template
c.JupyterHub.template_paths = [my_template_dir]

# pre_spawn_hook that raises our special error
def pre_spawn_hook(spawner):
    e = web.HTTPError(500, "this should show up, but won't")
    e.my_message = "This will show up"
    raise e

c.Spawner.pre_spawn_hook = pre_spawn_hook

We should have an Issue open about not re-wrapping HTTPErrors raised, only other errors. That would make it possible to do what @yuvipanda proposed, which could be further customized with a custom error template. It might be possible now, but it would require some extra fiddly bits.

2 Likes

Thanks @minrk ! That did indeed work.

Should I post an issue on the GitHub page to the effect you described?

@kellyrowland that’d be great!

:slightly_smiling_face:

3 Likes

Hi all,

I failed to follow up on this (oops!), and of course am now following up with a question. :slightly_smiling_face:

We tried a pre-spawn hook to the effect of:

async def setup(spawner):
  async with asyncssh.connect(remote_host) as conn:
    result = await conn.run(<cmd>)
    retcode = result.exit_status
  if retcode:
    e = web.HTTPError(507,reason="Insufficient Storage")
    e.my_message = "There is insufficient space."
    raise e

c.Spawner.pre_spawn_hook = setup 

In general, users for whom retcode=0 are able to login successfully.

For users that have retcode=1, the error message displays as expected upon their first login/spawn attempt. However, when a user makes a change such that retcode should then equal 0, they are still seeing the 507 error and message. This persists through stopping the server and also through the user logging out and then back in.

Does the pre-spawn hook not always happen before each individual spawn attempt? It’s not clear to me why this error seems to be “sticking”.

Thanks!

It should happen on each actual request to spawn, but if users are just visiting /user/theirname, then that will not trigger a new spawn if the most recent spawn request failed. This is to avoid problems where:

  • visits /user/theirname triggers spawn
  • spawn fails
  • refreshes /user/theirname triggers new spawn instead of showing the error

Because it is not known if a visit to /user/:name is refreshing the page from the failed request or a new one.

When a spawn fails, the user must go to the main page and click ‘start my server’ to request a new launch, which should always request a new spawn and call pre_spawn_hook.

That makes a lot of sense, thanks. Is there a variable or attribute to get the entire home URL in the jupyterhub_config.py file?

I was able to put a redirect to the home page in with the URL hardcoded (and I see how it’s done in the base handler), but I’m having difficulty getting that whole URL in the pre-spawn hook.

Try:

spawner.hub.base_url

This should be the absolute url path to the root of the hub (/hub/ by default) and ought to be the right thing for sending folks to the landing page.

1 Like

Thank you very much!

Something you could have a look at I’ve seen a few interesting and unique custom 404’s. I don’t understand why do more web owners not do this, it’s not hard. Look at https://under-the-open-sky.com/404/ You get the point. When this pops up you don’t seem to feel that bad.

Hi guys,

did something change in modern JH?

Workaround proposed by @minrk does not work for me.
Any exception e.g. tornado.web.HTTPError or basic RuntimeError raised in pre_spawn_hook leads to this message:
image

From the message I got that not_running.html template is used instead of error.html
Any ideas how to show up custom error message produced in pre_spawn_hook?