Jupyter "Error unpacking user from cookie" and "Clearing invalid/expired login cookie" (tested)
Error unpacking user from cookie in the Jupyter server log means the browser sent a login cookie the server can't use: its signature was valid but its contents were in another format. The more common sibling, Clearing invalid/expired login cookie, means the signature no longer matches. Both are reproduced below; logging in again fixes both.
1. Reproduce: a stale login cookie, two ways
run.py logs in to a Jupyter server with its token, restarts it with a new cookie secret, and reuses the old cookie. Then it sends a cookie that is correctly signed for the new server but holds a plain user id instead of the JSON user record current jupyter_server writes (the old format):
import os, subprocess, sys, time, urllib.request, urllib.error, http.cookiejar
# a stale login cookie: log in, restart the server with a new cookie secret, reuse the cookie
env = dict(os.environ, JUPYTER_DATA_DIR='/tmp/j410c/data', JUPYTER_RUNTIME_DIR='/tmp/j410c/rt', JUPYTER_CONFIG_DIR='/tmp/j410c/cfg')
os.makedirs('/tmp/j410c/root', exist_ok=True)
B = 'http://127.0.0.1:8963'
def start(n):
log = open(f'/tmp/j410c/server{n}.log', 'w')
p = subprocess.Popen([sys.executable, '-m', 'jupyter_server', '--port=8963', '--no-browser',
'--IdentityProvider.token=abc123', '--ServerApp.root_dir=/tmp/j410c/root'],
env=env, stdout=subprocess.DEVNULL, stderr=log)
for _ in range(100):
try: urllib.request.urlopen(B + '/api', timeout=1); break
except Exception: time.sleep(0.2)
return p
jar = http.cookiejar.CookieJar()
op = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
def get(label, path):
try: print(label, op.open(B + path, timeout=10).status)
except urllib.error.HTTPError as e: print(label, e.code)
s = start(1)
get('server 1, token in URL:', '/api/status?token=abc123')
get('server 1, cookie only: ', '/api/contents')
s.terminate(); s.wait()
# the cookie secret file lives in the runtime dir; deleting it = a new secret on next start
os.remove('/tmp/j410c/rt/jupyter_cookie_secret')
s = start(2)
get('server 2, old cookie: ', '/api/contents')
# a cookie signed with the CURRENT secret but holding a plain user id, the format
# classic Notebook (< 7) used, instead of the JSON user model jupyter_server expects
from tornado.web import create_signed_value
import uuid
import hmac, hashlib
key = open('/tmp/j410c/rt/jupyter_cookie_secret', 'rb').read()
secret = hmac.new(key, digestmod=hashlib.sha256).digest() # how jupyter_server derives it (no password set)
old = create_signed_value(secret, 'username-127-0-0-1-8963', uuid.uuid4().hex).decode()
req = urllib.request.Request(B + '/api/contents', headers={'Cookie': 'username-127-0-0-1-8963=' + old})
try: print('server 2, old-format cookie:', urllib.request.urlopen(req, timeout=10).status)
except urllib.error.HTTPError as e: print('server 2, old-format cookie:', e.code)
s.terminate(); s.wait()
print('--- server 2 log:')
for line in open('/tmp/j410c/server2.log'):
if 'cookie' in line.lower() or ' 403 ' in line: print(line.rstrip()[:150])
Output (jupyter_server 2.21.1, Python 3.13 on Linux):
server 1, token in URL: 200
server 1, cookie only: 200
server 2, old cookie: 403
server 2, old-format cookie: 403
--- server 2 log:
[I 2026-10-23 22:27:00.643 ServerApp] Writing Jupyter server cookie secret to /tmp/j410c/rt/jupyter_cookie_secret
[W 2026-10-23 22:27:00.786 ServerApp] Clearing invalid/expired login cookie username-127-0-0-1-8963
[W 2026-10-23 22:27:00.788 ServerApp] 403 GET /api/contents (@127.0.0.1) 2.50ms referer=None
[E 2026-10-23 22:27:00.843 ServerApp] Error unpacking user from cookie: Expecting value: line 1 column 1 (char 0)
[W 2026-10-23 22:27:00.843 ServerApp] Clearing invalid/expired login cookie username-127-0-0-1-8963
[W 2026-10-23 22:27:00.843 ServerApp] 403 GET /api/contents (@127.0.0.1) 0.74ms referer=None
2. What the two log messages mean
Clearing invalid/expired login cookie(warning): the cookie's signature doesn't match the server's cookie secret any more, so it is ignored and deleted. Here that happened because the secret filejupyter_cookie_secretin the runtime directory was gone and the server wrote a new one; in jupyter_server's source, changing the password also changes the secret.Error unpacking user from cookie: Expecting value: line 1 column 1 (char 0)(error): the signature was valid, but the value was not the JSON user record jupyter_server expects (json.loadsfailed). That is what an old-format cookie from an earlier Notebook/Jupyter version, or from a different Jupyter app on the same host and port with the same secret, looks like.
In both cases the server just treats the request as not logged in: the API answered 403 and the cookie was cleared. Nothing is broken on the server.
3. Fix
- Log in again: open the URL with
?token=...fromjupyter server list(orjupyter lab list). The new login cookie replaces the bad one, and the message stops. - If it keeps coming back, clear the cookies for that host and port in the browser. The cookie name includes them (
username-127-0-0-1-8963above), so two Jupyter servers on different ports don't clash, but an old server run on the same port can. - To keep logins valid across restarts, keep
jupyter_cookie_secretin place (don't wipe the runtime directory) or setServerApp.cookie_secret_fileto a stable path.
Related: Jupyter 403 Forbidden / _xsrf argument missing, Jupyter kernel not found.
Frequently asked questions
What does "Error unpacking user from cookie" mean in Jupyter?
The login cookie had a valid signature but was not the JSON user record jupyter_server expects, typically an old-format cookie. The server ignores it and treats the request as logged out.
How do I fix "Clearing invalid/expired login cookie" in Jupyter?
Log in again with the token URL from jupyter server list. It appears after the cookie secret changed, e.g. a restart with a new jupyter_cookie_secret file.
Is "Error unpacking user from cookie" harmful?
No. The server clears the cookie and asks for login (the API returns 403); after logging in again it stops.
Written with AI assistance. Measured on 2026-10-23 with jupyter_server 2.21.1 and Python 3.13 on Linux. The script ran verbatim and the output was pasted unedited. PlainTables is our extension; Jupyter is free.