Skip to content

ReferenceLook things up as you need them.

CLI and configuration ​

Every command, the server's flags and config file, environment variables, logging, and exit codes. ganymede <command> --help prints the same flags. The guides show them in use: Getting started, JupyterLab and VS Code, and Ganymede's app.

Commands ​

CommandDoes
kernelspec list, paths, install <dir>, remove <name>Find and manage kernelspecs, as jupyter kernelspec
runRun code as cells and print the output
consoleAn interactive console on a kernel
execute (exec)Run notebooks top to bottom, as jupyter execute
nb clearRemove a notebook's outputs
nb publishWrite a notebook as one self-contained HTML page; see Publishing a notebook
trustSign a notebook as trusted, as jupyter trust
grantA notebook's own host API grant
kernelStart a kernel and print its connection file, as jupyter kernel
attachRun code on a running kernel by its connection file
serveServe the Jupyter REST and websocket API
labserve with JupyterLab, opened in the browser
lab install, list, remove, clean, uninstallManage JupyterLab bundles
lab extension install, list, enable, disable, removeManage prebuilt JupyterLab extensions
ui, ui install, list, removeserve with ganymede-ui
mcp, mcp callNotebook tools for coding agents over MCP on stdio, or one tool call from a script; see Agents over MCP
infoVersions and the directories in use
updateReplace the binary with the latest release, as install.sh does; see Update and uninstall
completions <shell>Shell completions for bash, zsh, fish, powershell, elvish

The kernel name fake is a built-in in-process kernel for trying things out.

Flags for every command ​

FlagEffect
--kernelspec-root <DIR>Search this kernelspec root (a dir holding kernels/) first. Repeatable.
-q, --quietDiscard kernel processes' own stdout and stderr, not cell output.
-v, --verboseReport kernel and cell status on stderr; servers log at debug.
--startup-timeout <SECS>Seconds for a new kernel to answer; default 60 or config file.

Server flags ​

serve, lab and ui take these. Each sets the config file key of the same name in snake case, except where the last column says otherwise.

FlagEffectConfig key
--root-dirWhere notebooks and files are served from. Default: the current directory.
--ipAddress to listen on. Default 127.0.0.1; a non-loopback address also turns on --allow-remote-access.
--portDefault 8888. 0 picks a free port. lab and ui try up to 50 more while it is taken.
--tokenThe auth token. Unset generates one; an empty string disables auth.
--allow-remote-accessAccept requests whose Host is not a loopback address or a name in local_hostnames.
--allow-originAn origin, besides the server's own, allowed to use the API and websockets. Repeatable.allow_origins
--allow-credentialsLet pages on the --allow-origin origins use the login cookie. Off by default; ignored when * is allowed. See Security.allow_credentials
--base-urlA prefix for serving behind a reverse proxy, e.g. /jupyter/. Leading and trailing slashes are added when missing.
--cookie-secret-fileKey for signing login cookies. Default: jupyter_cookie_secret in the Jupyter runtime dir.
--default-kernelThe kernelspec new notebooks start. Default: the first kernelspec found.
--cull-idle-secsShut down kernels idle this long with no clients. 0 (the default) never does.
--kernel-restart-limitRestart a kernel that crashes, up to this many times in a row, before leaving it dead. Default 5; 0 never restarts. See Kernel crashes.kernel_restart_limit
--checkpoint-dirKeep checkpoints here instead of .ipynb_checkpoints beside each file.
--notary-dirWhere the notebook-signing key and trusted signatures live. Default: the Jupyter data dir.
--static-dirServe a frontend's files. JupyterLab's bundle is served at /lab, anything else at /. Default: the newest JupyterLab from ganymede lab install.
--lab-versionWhich installed JupyterLab to serve.
--labextensions-pathAnother directory of prebuilt JupyterLab extensions, searched first. Repeatable.labextensions_paths
--config-dirWhere frontend settings and workspaces are kept. Default: the Jupyter config dir ($JUPYTER_CONFIG_DIR, else ~/.jupyter).
--gateway-url, --gateway-tokenRun kernels on another server; see Gateway.
--kernelspec-rootAs above.kernelspec_roots
--startup-timeoutAs above.startup_timeout_secs
-q, --quietAs above.quiet
--mcpServe notebook tools for coding agents at /mcp, behind the token; see Agents over MCP.mcp_enabled
--config <file>The config file to read. Default: ganymede.toml in Ganymede's config dir, if it exists; see Config file.
--log-level, --log-fileSee Logging.

serve prints the URL, with its token, as the only line on stdout. Security covers what --ip 0.0.0.0 and --token "" expose.

Command flags ​

Each command's own flags, as its --help lists them. serve takes only the flags for every command and the server flags.

kernelspec list ​

FlagEffect
--jsonJSON in jupyter kernelspec list --json's shape.

kernelspec install ​

FlagEffect
--name <NAME>Name to install as. Defaults to the directory's name.
--prefix <PREFIX>Install under PREFIX/share/jupyter instead of the user data dir.
--replaceOverwrite an existing kernelspec of the same name.

kernelspec remove ​

FlagEffect
-f, --forceDo not ask first. Without a terminal on stdin nothing is asked.

run ​

FlagEffect
-k, --kernel <KERNEL>Kernelspec name, or fake.
--existing [<CONNECTION_FILE>]Connection file of a running kernel instead of launching one. A bare name such as kernel-<id>.json or <id> is also looked for in the Jupyter runtime dir; with no value, the newest kernel there.
--cwd <CWD>Working directory for a launched kernel.
-f, --file <FILE>File whose contents are one cell. Repeatable; runs after CODE.
--jsonPrint one JSON object per cell (status, count, nbformat outputs) instead of rendering output. A cell that could not finish (a timeout, a dead kernel) gets "status": "incomplete" and an error. The kernel's input requests get an empty reply.
--timeout <TIMEOUT>Seconds to wait for each cell. 0 waits forever. Default 0.
--interrupt-after-ms <MS>Interrupt each cell after this many milliseconds, to check how a kernel handles an interrupt.
--keep-goingKeep running cells after one fails.

console ​

FlagEffect
-k, --kernel <KERNEL>Kernelspec name, or fake.
--existing [<CONNECTION_FILE>]Connection file of a running kernel instead of launching one. A bare name such as kernel-<id>.json or <id> is also looked for in the Jupyter runtime dir; with no value, the newest kernel there.
--cwd <CWD>Working directory for a launched kernel.

execute ​

FlagEffect
-o, --output <OUTPUT>Where to write the result. - is stdout. {notebook_name} is replaced by the input's name without extension.
--inplaceOverwrite the input.
-k, --kernel <KERNEL>Kernelspec name. Defaults to each notebook's metadata.kernelspec.name.
--allow-errorsRecord errors and keep going.
--allow-error-name <ENAME>Error names that do not stop execution. Repeatable.
--force-raise-errorsStop on every error, overriding the allowances and raises-exception tags.
--timeout <TIMEOUT>Seconds per cell. 0 waits forever. Default 0.
--skip-tag <SKIP_TAG>Cells with this tag are skipped. Default skip-execution.
--coalesce-streamsMerge each cell's streams and resolve carriage returns.
--no-timingDo not record metadata.execution timings.
--no-widget-stateDo not save widget state in metadata.widgets.
-p, --param <NAME=VALUE>A parameter for the run, NAME=VALUE. Repeatable. VALUE is read as JSON when it parses (7, true, [1,2]), else as a string. The kernel gets them as a JSON object in GANYMEDE_PARAMS.
--params-file <FILE>Parameters from a file holding a JSON object; -p wins.
--resultsPrint each notebook's named results on stdout, as one JSON line: {"path", "status", "results"}.
--grant <KEY=PATH>A host API grant for the notebooks' kernels (docs/host-api.md): read=PATH a path they may read, run=PATH a notebook they may run. Repeatable. A key given replaces the config file's [host] default; PATH is relative to the current directory, "" is all of it.

kernel ​

FlagEffect
-k, --kernel <KERNEL>Kernelspec name, as ganymede kernelspec list shows it.
--cwd <CWD>Working directory for the kernel.

attach ​

FlagEffect
--timeout <TIMEOUT>Seconds to wait for each cell. 0 waits forever. Default 60.

nb clear ​

FlagEffect
-o, --output <OUTPUT>Where to write the result. - is stdout. {notebook_name} is replaced by the input's name without extension.
--inplaceOverwrite the input.

nb publish ​

FlagEffect
-o, --output <OUTPUT>Where to write the page; - is stdout. Default: the notebook's path with .html.
--code <CODE>How code shows: folded under a toggle, shown, or hidden. Default folded.
--byline <BYLINE>A line under the title, such as the date.
--no-downloadLeave the notebook copy and its Download button out.
--downloadPut the notebook copy in even with --code hidden.

The page replaces the notebook's directory with . and the home directory with ~, and nothing else: a secret a cell printed (a token, an API key, an environment variable) goes in as it is, so read the page before sharing it. See Publishing a notebook.

grant ​

FlagEffect
--listShow the notebook's grant, or every notebook's with no NOTEBOOK.
--revokeRemove what follows instead; with nothing after NOTEBOOK, its whole grant.
--root-dir <DIR>The server's root directory, which API paths are relative to. Default ..

trust ​

FlagEffect
--resetDelete the trusted signatures and generate a new key. Every notebook becomes untrusted.
--notary-dir <DIR>Directory with the key and signatures instead of the Jupyter data dir.

lab ​

FlagEffect
--no-browserPrint the URL without opening a browser.
--no-redirect-fileOpen the URL itself, token included, instead of the redirect page in the runtime dir: for snap or Flatpak browsers, which can't read it. jupyter_server's use_redirect_file = False.

And the server flags.

lab install ​

FlagEffect
--version <VERSION>JupyterLab version, exactly. A yanked release is refused. Default 4.6.6.
--forceReinstall if this version is already installed.
--no-default-extensionsSkip the extensions installed with JupyterLab by default: jupyterlab_widgets and anywidget, for widgets.
--latest-extensionsInstall the newest releases of the default extensions instead of the versions this release of Ganymede pins and checks.

lab extension install ​

FlagEffect
--forceReinstall the named packages if they are already installed, and let them replace extensions another package installed.
--no-depsInstall only the named packages, not the extensions they depend on.

lab extension list ​

FlagEffect
--jsonPrint a JSON array of {"name", "version", "extensions", "required_by"} objects instead.

lab extension disable ​

FlagEffect
--config-dir <DIR>The config dir ganymede serve --config-dir uses, if not the Jupyter config dir.

lab extension enable ​

FlagEffect
--config-dir <DIR>The config dir ganymede serve --config-dir uses, if not the Jupyter config dir.

lab list ​

FlagEffect
--jsonPrint a JSON array of {"version", "dir"} objects instead.

ui ​

FlagEffect
--no-browserPrint the URL without opening a browser.
--no-redirect-fileOpen the URL itself, token included, instead of the redirect page in the runtime dir: for snap or Flatpak browsers, which can't read it. jupyter_server's use_redirect_file = False.

And the server flags.

ui install ​

FlagEffect
--version <VERSION>ganymede-ui version, exactly. Default 0.10.1.
--forceReinstall if this version is already installed.

ui list ​

FlagEffect
--jsonPrint a JSON array of {"version", "dir"} objects instead.

mcp ​

FlagEffect
--root-dir <ROOT_DIR>The notebooks' directory. Default: the current directory.
--server <URL>Use this server (its URL with the base URL, such as http://localhost:8888/) instead of finding one.
--token <TOKEN>The token for --server.
--own-serverStart a server in this process even when one is running.
--read-onlyRead notebooks only: no edits, no execution. Implies --own-server.
--no-executeEdit notebooks but run no code. Implies --own-server.
--imagesSend images in outputs to the model. Implies --own-server.
--default-kernel <NAME>Kernelspec for new notebooks, on a server this command starts.

mcp call ​

FlagEffect
--client <CLIENT>The client name the server records in provenance. Default ganymede-mcp-call.

And mcp's flags.

mcp watch ​

FlagEffect
--client <CLIENT>The agent's client name: requests may name it (@claude for claude-code). Default claude-code.
--every <EVERY>Seconds between checks. Default 2.
--settle <SETTLE>Seconds a request (or, with --edits, a notebook's edits) must stay unchanged before it is printed. Default 3.
--editsAlso print a line when a person's edits to a notebook settle (a spike, off by default).

And mcp's flags.

info ​

FlagEffect
--jsonPrint one JSON object instead.

update ​

FlagEffect
--version <X.Y.Z>Install this release instead of the latest. It may be older.
--mainInstall the latest build of main instead of a release.
--checkOnly say whether a newer release is available.
--forceReinstall even when this version is already installed.

Config file ​

A TOML file of the keys above, passed with --config. Without it, ganymede.toml in Ganymede's config dir is read if it exists:

OSFile
Linux, macOS$XDG_CONFIG_HOME/ganymede/ganymede.toml, else ~/.config/ganymede/ganymede.toml
Windows%APPDATA%\ganymede\ganymede.toml

GANYMEDE_CONFIG_DIR names the directory instead, on any OS: a service can set it to choose its file.

ganymede info prints the path. Flags override the file, and an unknown key is an error, so a misspelt token can't start a server with a random one.

NOTE

Unreleased. Available only from a build of main (HEAD), not in the latest release. The latest release reads ganymede.toml from the Jupyter config dir ($JUPYTER_CONFIG_DIR, else ~/.jupyter). A build of main still reads it there when Ganymede's config dir has none, and warns that it should move. A setup that chose its file with JUPYTER_CONFIG_DIR should set GANYMEDE_CONFIG_DIR to the same directory, or a ganymede.toml in the user's ~/.config/ganymede wins over it.

toml
root_dir = "/srv/notebooks"
port = 9000
default_kernel = "cdp-node"
cull_idle_secs = 3600
allow_origins = ["https://notebooks.example.org"]

Every key, with its default:

toml
# ganymede.toml: every key, set to its default or commented out.
# Flags given on the command line override these.
ip = "127.0.0.1"
port = 8888
# token = "..."                  # unset: a new random token each start; "" turns auth off
root_dir = "."                   # the current directory
# checkpoint_dir = "/path"       # unset: .ipynb_checkpoints beside each file
# notary_dir = "/path"           # unset: the Jupyter data dir
# cookie_secret_file = "/path"   # unset: jupyter_cookie_secret in the runtime dir
# static_dir = "/path"           # unset: the newest JupyterLab from `ganymede lab install`
# lab_version = "4.6.6"
labextensions_paths = []
# config_dir = "/path"           # unset: the Jupyter config dir
base_url = "/"
kernelspec_roots = []
# default_kernel = "cdp-node"    # unset: the first kernelspec found
cull_idle_secs = 0               # 0: never
kernel_restart_limit = 5         # 0: a crashed kernel stays dead
startup_timeout_secs = 60
quiet = false
allow_origins = []
allow_credentials = false
# allow_remote_access = true     # unset: on when ip isn't a loopback address
local_hostnames = ["localhost"]
trust_xheaders = false
max_body_size = 536870912        # 512 MiB
# gateway_url = "https://kernels.example.org"
# gateway_token = "..."
terminals_enabled = true         # false: no /api/terminals
terminal_shell = []              # empty: $SHELL -l, or powershell.exe on Windows
mcp_enabled = false              # true: notebook tools for agents at /mcp
mcp_read_only = false            # /mcp reads notebooks only
mcp_no_execute = false           # /mcp edits notebooks but runs no code
mcp_images = false               # /mcp sends images in outputs to the model
# mcp_token = "..."              # a token only /mcp accepts (see MCP)
crash_reporting = false          # true: send crash reports (see Privacy)
frame_ancestors = "'self'"       # who may frame the server's pages

# Extra response headers for files from a static_dir that
# isn't JupyterLab, none by default (see Static headers). One table per path; "/assets/*" matches
# every path under /assets/:
# [static_headers."/kernel.html"]
# Cross-Origin-Opener-Policy = "same-origin"
# Cross-Origin-Embedder-Policy = "require-corp"

# What cells may read and edit through the host API (see A host API for kernels):
# [host]
# read = [""]                    # API paths; "" is the root, [] only the cell's own notebook
# edit_self = true               # self.add/edit/delete: a notebook edits its own cells
# run = []                       # notebooks a notebook may run (notebooks.run); [""] is all

# Tailnet logins (and with whois, tags) let in without the token, none by
# default (see Security, Tailnet identity):
# [tailscale]
# allow = ["you@example.com"]
# whois = false                  # identify tailnet addresses with `tailscale whois`
# cli = "tailscale"              # the tailscale command for whois

# Language servers for jupyterlab-lsp, none by default. One table each:
# [language_servers.pyright]
# argv = ["pyright-langserver", "--stdio"]
# languages = ["python"]
# mime_types = ["text/x-python"]

These eight keys have no flag. The terminal keys, the MCP keys other than mcp_enabled, static_headers and language_servers have none either; the sections below and Agents over MCP cover them.

KeyEffect
local_hostnamesHost names, or IP addresses, accepted besides loopback addresses while remote access is off. Default ["localhost"].
trust_xheadersTrust X-Forwarded-Proto and X-Scheme from a reverse proxy, so login cookies sent over proxied HTTPS are marked Secure.
max_body_sizeLargest request body, in bytes, which caps a notebook's size. A /login form gets at most 64 KiB. Also the largest message a document room takes, and how many bytes of messages a room client may fall behind before it is closed, so lowering it to cap uploads also closes slow room clients sooner. Default 512 MiB.
crash_reportingSend crash reports from ganymede to Sentry; see Privacy and crash reporting. Default false.
[tailscale] allowTailnet login names let in without the token when tailscale serve forwards their requests to a loopback port, and with whois tags (tag:agent). Default [], off. See Security.
[tailscale] whoisIdentify requests from tailnet addresses with tailscale whois. Needs ip set to this machine's tailnet address; the server refuses to start otherwise. Default false. See Security.
[tailscale] cliThe tailscale command whois runs. Default tailscale on PATH.
frame_ancestorsThe CSP sources allowed to frame the server's pages, added to every response as frame-ancestors <sources>. Default 'self', as jupyter_server; add an origin, such as 'self' https://portal.example.com, to embed /lab in that site. See Security.

NOTE

Unreleased. [tailscale], and IP addresses in local_hostnames, are available only from a build of main (HEAD), not in the latest release.

The host API ​

[host] read lists the API paths that cell code may read through the host API: notebooks.list, notebooks.read, notebooks.search and notebooks.results. A path covers itself and everything under it, by whole segments, so "journal" covers journal/a.ipynb but not journal-private/. The default, [""], is the whole root; [] leaves each notebook only itself. A path is matched where it leads once symlinks are resolved, so a symlink in a granted folder doesn't reach another.

[host] edit_self, on by default, lets a notebook add, edit and delete its own cells (self.add, self.edit, self.delete), through its document room, so every client sees the change. An edit or delete names the cell's source hash from notebooks.read, and fails with source_mismatch when the cell changed since. false takes the calls away from every notebook that doesn't have its own grant.

[host] run lists the notebooks a notebook may run with notebooks.run, matched as read is; the default, [], is none. A run uses POST /api/notebooks/run's code: a fresh kernel from the notebook's own kernelspec, the parameters in GANYMEDE_PARAMS, and { path, status, results, error? } back, with nothing written. Its kernel gets the intersection of the caller's grant and the run notebook's, and no self.* calls. Runs nest at most 3 deep, a kernel has at most 2 runs in flight (more wait), and cancelling the call shuts the run's kernel down.

A notebook's own grant replaces these defaults for the keys it sets. ganymede grant stores it by the notebook's file id, beside the server's file id database in the Jupyter data dir, so it follows a rename made through the server and goes when the notebook is deleted through it. A notebook deleted outside the server leaves its grant with the path, so a new file saved there gets it; --revoke it first. A running server sees a change on the next call; a notebooks.run keeps the grant it started with until it ends.

sh
ganymede grant journal/index.ipynb read=journal   # reads only journal/ (and itself)
ganymede grant downloaded.ipynb edit_self=false
ganymede grant journal/index.ipynb run=reports   # may run notebooks in reports/
ganymede grant --list                             # every notebook's grant
ganymede grant --revoke journal/index.ipynb read=journal
ganymede grant --revoke downloaded.ipynb          # back to the defaults

read=PATH adds a path to the notebook's own read list, which replaces [host] read for it, so it can narrow the default or widen it; run=PATH does the same for [host] run. Revoking every read path leaves the notebook only itself; --revoke NOTEBOOK read (or run) goes back to the default. --root-dir names the server's root when it isn't the current directory, since grants are kept per root.

Clients such as the apps set the same grants over HTTP, with the server's token:

http
GET    /api/host/grants/journal/index.ipynb
PUT    /api/host/grants/journal/index.ipynb
{"read": ["journal"], "run": ["reports"]}
DELETE /api/host/grants/journal/index.ipynb

Each answers { path, grant, defaults }: the notebook's own grant ({} when it has none) and [host] from the config, which it replaces key by key. PUT stores the grant in place of the one the notebook had, and {} or DELETE removes it. A grant has only read, edit_self and run; anything else is 400, as are a grant path with .. and a path that isn't a notebook. A missing notebook is 404, and a server whose file ids live only in memory answers 409, since a grant there wouldn't outlive it. A kernel holds no token, so it can't grant itself anything.

Under ganymede execute ​

ganymede execute answers its kernels' host calls too, with no server running. API paths are relative to the current directory, and a notebook under it is its kernel's own notebook. The defaults are [host] from the config file, with --grant in place of each key it names, and a notebook's own grant from ganymede grant (with the current directory as its root) replaces them key by key, as on a server. --grant read=PATH and --grant run=PATH are repeatable and take paths as ganymede grant does; "" is the whole current directory. An executed notebook's kernel has no self.* calls, whatever its grant, since execute writes the notebook itself when the run ends. A notebooks.run starts its kernel from the kernelspecs serve would use with the same config and --kernelspec-root flags, and shuts it down when it answers. A config file that doesn't parse, like a host that can't start, leaves the run without the host API, with a warning.

sh
ganymede execute index.ipynb --grant read=journal --grant run=reports

Static headers ​

static_headers adds response headers to files served from a static_dir that isn't JupyterLab. Keys are paths under base_url; a key ending in * matches every path that starts with the rest of it. Keys match the request path, and also the path as the file is found: percent-decoded, without empty or . segments, and with index.html added to one ending in /. Nothing is added by default. Routes the server answers itself, such as /api/status, never get these headers; a request no route claims, such as an unknown /api/ path, falls through to the static files and can. When several keys set the same header, the last key in sorted order wins, and a configured header replaces one the server set. The frame_ancestors directive is appended to a Content-Security-Policy set here unless it already has a frame-ancestors directive, so a page can set its own.

A page that needs SharedArrayBuffer, such as one hosting a kernel in a web worker, must be cross-origin isolated:

toml
[static_headers."/kernel.html"]
Cross-Origin-Opener-Policy = "same-origin"
Cross-Origin-Embedder-Policy = "require-corp"

An app embedding the server passes the same object in its JSON config ("static_headers": {"/kernel.html": {...}}). The server refuses to start when a key doesn't begin with / or a header name or value isn't valid HTTP.

Kernel crashes ​

A kernel that exits without being shut down or restarted, through the API or by a client's shutdown_request, is restarted under the same id, in the same directory, as jupyter_server's KernelRestarter does. Its clients see:

  1. For the request it was running, a reply with status: error and ename: KernelDied (and an error output, for a cell); for requests queued behind it, status: aborted replies. Each is followed by status: idle, so clients stop waiting for them.
  2. status: restarting, which JupyterLab shows as an automatic restart and reconnects on.
  3. The new kernel's own statuses.

A kernel that crashes again within ten seconds of being restarted counts toward kernel_restart_limit (default 5). One crash past the limit, it is reported status: dead and removed, as it always is with the limit set to 0. The kernel's state is lost in a restart, as it is under Jupyter.

Gateway ​

--gateway-url runs kernels on another Jupyter or Ganymede server while notebooks and files stay local, as jupyter_server --gateway-url does:

sh
ganymede serve --root-dir . \
  --gateway-url https://kernels.example.org --gateway-token "$TOKEN"

Running kernels on another server. On your machine, a frontend talks to ganymede serve started with --gateway-url, which answers contents, sessions and trust from the notebook folder. For kernelspecs and kernels it calls the kernel server's REST API (kernelspecs, start, interrupt, restart, shut down) and relays each kernel's messages over its websocket. The kernel server, jupyter server or ganymede serve, runs the kernel processes over ZeroMQ.Running kernels on another server. On your machine, a frontend talks to ganymede serve started with --gateway-url, which answers contents, sessions and trust from the notebook folder. For kernelspecs and kernels it calls the kernel server's REST API (kernelspecs, start, interrupt, restart, shut down) and relays each kernel's messages over its websocket. The kernel server, jupyter server or ganymede serve, runs the kernel processes over ZeroMQ.

Kernelspecs come from that server (/api/kernelspecs, refreshed every minute); starting, interrupting, restarting and shutting down a kernel go through its REST API, and kernel messages through its websocket. HTTPS is checked against the system's trust store.

The token may be left out when the kernel server is a Ganymede server that lets this machine in by tailnet identity: the gateway marks its requests as a program's (Ganymede-Client).

NOTE

Unreleased. Available only from a build of main (HEAD), not in the latest release: the gateway without a token on a tailnet.

Server info file ​

While it runs, serve, lab and ui write jpserver-<pid>.json in the Jupyter runtime dir, as jupyter_server does, so tools that look for a running Jupyter server find Ganymede too: jupyter server list lists it, and an agent or script can read the URL and token from it instead of being given them.

json
{
  "base_url": "/",
  "ganymede_version": "0.11.0",
  "hostname": "127.0.0.1",
  "password": false,
  "pid": 41237,
  "port": 8888,
  "root_dir": "/Users/me/notebooks",
  "secure": false,
  "sock": "",
  "token": "3f9c...",
  "url": "http://127.0.0.1:8888/",
  "version": "2.21.1"
}

The keys are jupyter_server's; version is the jupyter_server version whose API Ganymede matches (as GET /api reports), and ganymede_version tells a client it is Ganymede. token is empty when auth is off. Beside it, jpserver-<pid>-open.html redirects a browser to the server with the token. Both are readable only by you, since they hold the token, and are removed when the server stops; a file left by a server that was killed names a process that is gone, which jupyter server list checks and cleans up. A server embedded in an app writes neither.

From ganymede-ui 0.11.0 on, the Mac app writes jpserver-<pid>.json (no open page) while its MCP setting is on, so ganymede mcp finds it. Its token is the server's mcp_token, which opens only /mcp, so jupyter server list and other readers of the file get a URL the server refuses.

lab and ui open the browser on jpserver-<pid>-open.html, as jupyter_server does, so the token is not in the browser command's arguments. Snap and Flatpak browsers, the default Firefox and Chromium on Ubuntu 22.04 and later, can't read the runtime dir and show an access-denied page; --no-redirect-file opens the URL itself instead, as jupyter_server's use_redirect_file = False does.

Environment variables ​

VariableEffect
GANYMEDE_CONFIG_DIRGanymede's config dir, which holds ganymede.toml; see Config file.
XDG_CONFIG_HOMEHolds Ganymede's config dir, ganymede/, on Linux and macOS, when GANYMEDE_CONFIG_DIR isn't set. Default ~/.config.
JUPYTER_CONFIG_DIRThe Jupyter config dir, for JupyterLab's settings and workspaces. Default ~/.jupyter.
JUPYTER_DATA_DIRThe user Jupyter data dir, where kernelspecs and signatures are.
JUPYTER_PATHMore data dirs to search for kernelspecs, first.
JUPYTER_RUNTIME_DIRWhere connection files, the cookie secret and the server info file go. Default: runtime/ in the data dir.
JUPYTER_GATEWAY_URL, JUPYTER_GATEWAY_AUTH_TOKEN--gateway-url and --gateway-token when not given.
GANYMEDE_DATA_DIRWhere lab install and ui install put what they download.
GANYMEDE_PYPI_URL, GANYMEDE_PYPI_HOSTSA PyPI mirror, and more hosts allowed to serve wheels; see What a download trusts.
GANYMEDE_UI_URLA mirror of ganymede-ui's release downloads.
GANYMEDE_DOWNLOAD_BASEA mirror of Ganymede's release downloads, for update and install.sh.
GANYMEDE_CRASH_REPORTING1 turns crash reporting on and 0 off, overriding crash_reporting; see Privacy and crash reporting.
RUST_LOGThe log filter, when --log-level isn't given.
HTTPS_PROXYUsed for downloads.

NOTE

Unreleased. Available only from a build of main (HEAD), not in the latest release: GANYMEDE_CONFIG_DIR and XDG_CONFIG_HOME. The latest release reads ganymede.toml from JUPYTER_CONFIG_DIR.

ganymede info prints every directory in use.

What a kernel gets ​

A kernel inherits the server's environment, then its kernelspec's env, and gets two more, as under jupyter_server:

VariableValue
JPY_PARENT_PIDThe server's process id.
JPY_SESSION_NAMEFor a kernel started for a notebook (a session): the notebook's path on disk, so code can find the notebook it runs in. A session renamed or moved changes it on the kernel's next restart. For a kernel started on its own: the kernel id.

Parameters and named results ​

ganymede execute -p and --results make a notebook a program: parameters in, named results out. Neither names a language; any kernel that reads the variable and writes the MIME type takes part.

WhatFormat
GANYMEDE_PARAMSSet on every kernel execute and POST /api/notebooks/run start: the parameters from -p and --params-file as one JSON object, at most 32,767 bytes, or {} without them, so a kernel never inherits a parent run's. The executed notebook records them in metadata.ganymede.params, and a run without them removes that key.
application/vnd.ganymede.result+jsonA named result: a display_data or execute_result output whose bundle holds { "name": string, "value": JSON }. Add a text/plain entry for frontends that don't know the type.
application/scrapbook.scrap.json+json, application/scrapbook.scrap.text+jsonPython scrapbook's scraps (sb.glue), read as named results from their name and data.

When a name appears more than once, the last output wins, as in scrapbook. --results prints one JSON line per notebook: {"path", "status": "ok" | "error", "results": {name: value}, "error"?}. The results come only from cells the run executed, so a failed run reports what it published before the failure, never what an earlier run saved in later cells or in cells skipped by tag. A run that stops on a signal, or whose notebook can't be saved, prints its line and ends the command.

POST /api/notebooks/run is the same over HTTP, for clients such as the apps' links and Shortcuts:

http
POST /api/notebooks/run
{"path": "reports/daily.ipynb", "params": {"day": "2026-11-10"}, "timeout": 600}

It runs the notebook in a fresh kernel from its own kernelspec, with params (a JSON object, optional) in GANYMEDE_PARAMS, and answers when the run ends with the body of one --results line. path is an API path; the notebook is read from its room when one is open, so unsaved edits run. The notebook and its room are never written. timeout is in seconds for the whole run, at most and by default 3600. The run's kernel is listed in /api/kernels while it runs, and shut down when the run ends, times out, or the client disconnects. A missing path, bad params or timeout, or a notebook with no metadata.kernelspec.name, or one this server doesn't have, is 400, and a notebook that can't be read 404, before any kernel starts; after that, a failure is 200 with "status": "error".

Run copies ​

"save": "copy" keeps the executed notebook. The copy goes under runs/ at the root, in a folder named for the notebook's path without .ipynb, named for the run's start in UTC, and the answer names it as copy:

http
POST /api/notebooks/run
{"path": "reports/daily.ipynb", "params": {"day": "2026-11-10"}, "save": "copy"}

{"path": "reports/daily.ipynb", "status": "ok", "results": {...},
 "copy": "runs/reports/daily/2026-11-11T193005Z.ipynb"}

The copy holds every cell's outputs and the parameters in metadata.ganymede.params. A failed run is kept too, so the copy shows where it stopped: cells the run didn't reach have no outputs. A second run in the same second gets -2, -3, and so on. The source notebook is unchanged. save defaults to false, which writes nothing; any other value is 400. runs/ is an ordinary folder: browse, read or delete copies through the contents API, and leave it out of a notebook's [host] read grant to keep runs out of its reads and searches. The host API's notebooks.run takes the same save, and keeps a copy only when the calling notebook may read the notebook it runs as well as run it. A read grant on runs/ reads every copy there, whoever made it, so it is a read of each run notebook's source and outputs. A leading dot in a folder or notebook name becomes _ in the copy's folder (.private/nb.ipynb goes to runs/_private/nb/), so copies are never hidden files.

Logging ​

serve, lab and ui log at info level on stderr; -v raises it to debug, and RUST_LOG overrides both. --log-level (trace, debug, info, warn, error, or Jupyter's WARNING and CRITICAL) overrides all three, and --log-file appends the log to a file instead of stderr. Kernels' own output lines start with [kernel <first 8 of its id>].

Exit status ​

StatusMeaning
0Success
1A cell failed, a kernel timed out or died, or a file or download failed (as jupyter execute)
2Usage error: an unknown flag, a missing argument, no code to run
128+NStopped by signal N: 130 for Ctrl-C, 143 for SIGTERM, 129 for SIGHUP

MIT license.