Jupyter "Error Starting Kernel" (500 Unhandled error): three causes, tested

Error Starting Kernel in Jupyter usually means the interpreter in the kernel's kernel.json is gone, lacks ipykernel, or the spec file is broken. The server answers 500 Unhandled error and only its log says why. All three cases are reproduced below, plus a script that checks every kernelspec.

1. Reproduce: four kernelspecs, three ways to fail

run.py writes four kernel specs (a working one, one pointing at a deleted virtualenv, one pointing at a Python without ipykernel, one with broken kernel.json), starts a Jupyter server, and asks it to start each kernel through the same REST call the notebook UI uses when you pick a kernel:

import json, os, subprocess, sys, time, urllib.request, urllib.error
# kernelspecs that fail in three different ways, plus one that works
D = '/tmp/ks412'
def spec(name, argv):
    os.makedirs(f'{D}/data/kernels/{name}', exist_ok=True)
    json.dump({'argv': argv, 'display_name': name, 'language': 'python'},
              open(f'{D}/data/kernels/{name}/kernel.json', 'w'))
CF = ['-m', 'ipykernel_launcher', '-f', '{connection_file}']
spec('good', [sys.executable] + CF)
spec('deleted-venv', ['/tmp/ks412/oldenv/bin/python'] + CF)   # path that no longer exists
spec('no-ipykernel', ['/usr/bin/python3'] + CF)                  # interpreter without ipykernel
spec('bad-json', [sys.executable] + CF)
open(f'{D}/data/kernels/bad-json/kernel.json', 'w').write('{"argv": [,]}')
os.makedirs(f'{D}/root', exist_ok=True)
env = dict(os.environ, JUPYTER_DATA_DIR=f'{D}/data', JUPYTER_RUNTIME_DIR=f'{D}/rt', JUPYTER_CONFIG_DIR=f'{D}/cfg',
           JUPYTER_PATH=f'{D}/data')
srv = subprocess.Popen([sys.executable, '-m', 'jupyter_server', '--port=8963', '--no-browser',
                        '--IdentityProvider.token=abc123', f'--ServerApp.root_dir={D}/root'],
                       env=env, stdout=subprocess.DEVNULL, stderr=open(f'{D}/server.log', 'w'))
B = 'http://127.0.0.1:8963'
H = {'Authorization': 'token abc123', 'Content-Type': 'application/json'}
for _ in range(100):
    try: urllib.request.urlopen(B + '/api', timeout=1); break
    except Exception: time.sleep(0.2)

def call(method, path, body=None):
    req = urllib.request.Request(B + path, method=method, headers=H,
                                 data=json.dumps(body).encode() if body is not None else None)
    try:
        r = urllib.request.urlopen(req, timeout=30); return r.status, json.loads(r.read() or b'null')
    except urllib.error.HTTPError as e:
        return e.code, json.loads(e.read() or b'null')

st, specs = call('GET', '/api/kernelspecs')
print('kernelspecs listed:', sorted(specs['kernelspecs']))
for name in ['good', 'deleted-venv', 'no-ipykernel', 'bad-json']:
    st, body = call('POST', '/api/kernels', {'name': name})
    msg = body.get('message') if isinstance(body, dict) else body
    print(f'POST /api/kernels {name!r}: {st}', (msg or '')[:110] if st != 201 else 'id=' + body['id'][:8] + ' state=' + body['execution_state'])
    if st == 201:
        last = None
        for t in range(25):
            st2, k = call('GET', '/api/kernels/' + body['id'])
            cur = f'GET {st2} ' + (k['execution_state'] if st2 == 200 else k.get('message', ''))
            if cur != last: print(f'   after {t:>2}s: {cur}'); last = cur
            if name == 'good' and t == 5: break
            time.sleep(1)
srv.terminate()
try: srv.wait(timeout=5)
except subprocess.TimeoutExpired: srv.kill(); srv.wait()
print('--- server log:')
for line in open(f'{D}/server.log'):
    if any(w in line for w in ('Error loading', 'Failed to run', 'Errno 2', 'No module', 'Restarter', 'restart', 'dead', '500 POST', 'Expecting value')):
        print(line.rstrip()[:150].replace(line[3:27], 'TIME ') if line.startswith('[') else line.rstrip()[:150])

Output (jupyter_server 2.21.1, jupyter_client 8.10.0, ipykernel 7.4.0, Python 3.13 on Linux):

kernelspecs listed: ['deleted-venv', 'good', 'no-ipykernel', 'python3']
POST /api/kernels 'good': 201 id=fd3141f0 state=starting
   after  0s: GET 200 starting
POST /api/kernels 'deleted-venv': 500 Unhandled error
POST /api/kernels 'no-ipykernel': 201 id=cc8d1bbd state=starting
   after  0s: GET 200 starting
   after 18s: GET 404 Kernel does not exist: cc8d1bbd-bdcc-489c-926e-875be586e604
POST /api/kernels 'bad-json': 500 Unhandled error
--- server log:
[W TIME ServerApp] Error loading kernelspec 'bad-json'
        raise JSONDecodeError("Expecting value", s, err.value) from None
    json.decoder.JSONDecodeError: Expecting value: line 1 column 11 (char 10)
[E TIME ServerApp] Failed to run command:
[E TIME ServerApp] [Errno 2] No such file or directory: '/tmp/ks412/oldenv/bin/python'
    FileNotFoundError: [Errno 2] No such file or directory: '/tmp/ks412/oldenv/bin/python'
[E TIME ServerApp] [Errno 2] No such file or directory: '/tmp/ks412/oldenv/bin/python'
    FileNotFoundError: [Errno 2] No such file or directory: '/tmp/ks412/oldenv/bin/python'
    FileNotFoundError: [Errno 2] No such file or directory: '/tmp/ks412/oldenv/bin/python'
[E TIME ServerApp] 500 POST /api/kernels (929623700111435bb2c724150e86199d@127.0.0.1) 14.51ms referer=None
/usr/bin/python3: No module named ipykernel_launcher
[I TIME ServerApp] AsyncIOLoopKernelRestarter: restarting kernel (1/5), new random ports
/usr/bin/python3: No module named ipykernel_launcher
[I TIME ServerApp] AsyncIOLoopKernelRestarter: restarting kernel (2/5), new random ports
/usr/bin/python3: No module named ipykernel_launcher
[I TIME ServerApp] AsyncIOLoopKernelRestarter: restarting kernel (3/5), new random ports
/usr/bin/python3: No module named ipykernel_launcher
[I TIME ServerApp] AsyncIOLoopKernelRestarter: restarting kernel (4/5), new random ports
/usr/bin/python3: No module named ipykernel_launcher
[I TIME ServerApp] AsyncIOLoopKernelRestarter: restarting kernel (5/5), new random ports
/usr/bin/python3: No module named ipykernel_launcher
[W TIME ServerApp] AsyncIOLoopKernelRestarter: restart failed
[E TIME ServerApp] Expecting value: line 1 column 11 (char 10)
        raise JSONDecodeError("Expecting value", s, err.value) from None
    json.decoder.JSONDecodeError: Expecting value: line 1 column 11 (char 10)
[E TIME ServerApp] Expecting value: line 1 column 11 (char 10)
        raise JSONDecodeError("Expecting value", s, err.value) from None
    json.decoder.JSONDecodeError: Expecting value: line 1 column 11 (char 10)
        raise JSONDecodeError("Expecting value", s, err.value) from None
    json.decoder.JSONDecodeError: Expecting value: line 1 column 11 (char 10)
[E TIME ServerApp] 500 POST /api/kernels (b1c6d42326eb4947b23dcd2feac0cc1d@127.0.0.1) 10.23ms referer=None

2. Which failure you have

So for "Error Starting Kernel" with no details, the browser can't tell you more: read the terminal where jupyter lab / jupyter notebook runs, or its log.

3. Check every kernelspec at once

check.py loads each spec the way Jupyter does, then checks the interpreter exists and can import ipykernel. A bare python in argv is fine: jupyter_client replaces it with the server's own interpreter (sys.executable):

# check every kernelspec Jupyter can see: does argv[0] exist, and can it import ipykernel?
import os, subprocess, sys
from jupyter_client.kernelspec import KernelSpecManager
ksm = KernelSpecManager()
for name, path in sorted(ksm.find_kernel_specs().items()):
    try:
        argv = ksm.get_kernel_spec(name).argv
    except Exception as e:
        print(f'{name:13} BROKEN kernel.json: {type(e).__name__}: {e}'); continue
    exe = argv[0]
    if exe in ('python', 'python3', f'python{sys.version_info[0]}.{sys.version_info[1]}'):
        exe = sys.executable   # jupyter_client swaps a bare 'python' for the server's own interpreter
    if not os.path.exists(exe):
        print(f'{name:13} MISSING interpreter {exe}'); continue
    r = subprocess.run([exe, '-c', 'import ipykernel; print(ipykernel.__version__)'], capture_output=True, text=True)
    print(f'{name:13}', f'OK ipykernel {r.stdout.strip()}' if r.returncode == 0 else f'NO ipykernel in {exe}')
bad-json      BROKEN kernel.json: JSONDecodeError: Expecting value: line 1 column 11 (char 10)
deleted-venv  MISSING interpreter /tmp/ks412/oldenv/bin/python
good          OK ipykernel 7.4.0
no-ipykernel  NO ipykernel in /usr/bin/python3
python3       OK ipykernel 7.4.0

4. Fix

jupyter kernelspec remove -y deleted-venv      # the env is gone: drop its spec
/usr/bin/python3 -m ipykernel --version        # this interpreter has no ipykernel
jupyter kernelspec list 2>/dev/null            # (stderr still warns about bad-json)
Removed /tmp/ks412/data/kernels/deleted-venv
/usr/bin/python3: No module named ipykernel
Available kernels:
  bad-json        /tmp/ks412/data/kernels/bad-json
  good            /tmp/ks412/data/kernels/good
  no-ipykernel    /tmp/ks412/data/kernels/no-ipykernel
  python3         /tmp/jn408/ve/share/jupyter/kernels/python3

Related: Jupyter kernel not found (NoSuchKernel), Jupyter kernel keeps dying, Jupyter 403 Forbidden.

Frequently asked questions

Why does Jupyter say "Error Starting Kernel"?

The server could not launch the command in the kernel's kernel.json, most often because that Python interpreter (a deleted venv or conda env) no longer exists. The 500 response only says "Unhandled error"; the server log shows FileNotFoundError with the path.

Why is my Jupyter kernel stuck on starting and then dies?

The interpreter exists but has no ipykernel: it exits with "No module named ipykernel_launcher", Jupyter restarts it 5 times, then gives up (about 18 seconds in our test). Install ipykernel into that interpreter.

How do I find which kernel is broken?

Run jupyter kernelspec list and check each argv path in kernel.json exists and can import ipykernel, or use the check script in this guide.

Written with AI assistance. Measured on 2026-10-23 with jupyter_server 2.21.1, jupyter_client 8.10.0, ipykernel 7.4.0 and Python 3.13 on Linux. The scripts ran verbatim and the output was pasted unedited. PlainTables is our extension; Jupyter is free.