Troubleshooting (hooks)#
This page is a symptom-based guide for when notebook embedding + hooks don’t behave as expected.
Rule of thumb: most failures are connectivity/proxy issues, not Python hook code.
First step (always): collect a debug report#
Run:
report = viewer.debug_connection()
report
This report includes:
server probes (
/_cellucid/health,/_cellucid/info,/_cellucid/datasets)ping/pong roundtrip (Python → iframe → Python)
a frontend “debug snapshot” (iframe URL/origin/user agent)
recent frontend warnings/errors forwarded to Python (
consoleevents)
If you include this report when asking for help, debugging is usually 10× faster.
Symptom: iframe shows “Cellucid viewer UI could not be loaded”#
Symptom#
The iframe loads an error page saying the viewer UI could not be loaded.
Likely causes (ordered)#
No outbound network access to
https://www.cellucid.com(first run).You are offline and the UI cache is empty.
Cache directory is not writable/persistent.
How to confirm#
In the debug report, check:
report["web_ui"]["cache"]report["viewer_index_probe_error"](proxy fetch failure)
Try opening in a browser:
<viewer.server_url>/index.html
Fix#
If you can go online once:
re-run the cell; Cellucid will prefetch UI assets with a progress bar.
If you need a persistent cache directory:
set
CELLUCID_WEB_PROXY_CACHE_DIRto a writable location.
If you suspect a bad cache:
viewer.clear_web_cache()and retry.
Prevention#
On shared/HPC environments, set
CELLUCID_WEB_PROXY_CACHE_DIRto a persistent path.Prefetch once in a “setup notebook”:
viewer.ensure_web_ui_cached()
If UI assets cannot be fetched or found in the cache, the iframe shows an error page with next steps.#
Symptom: iframe shows “notebook proxy required”#
Symptom#
The iframe shows a page saying a notebook proxy is required (or similar messaging).
Likely causes#
Your notebook frontend is served over HTTPS (or is remote), so direct
http://127.0.0.1:<port>is blocked/unreachable.jupyter-server-proxyis not installed/enabled.
How to confirm#
In browser devtools console/network, look for mixed-content blocking.
In the debug report, check
report["jupyter_server_proxy"].
Fix (recommended order)#
Install/enable
jupyter-server-proxyon the Jupyter server.If your kernel is remote and your browser is local: use SSH port forwarding.
Advanced: set
CELLUCID_CLIENT_SERVER_URLto a browser-reachable HTTPS URL for the server.
Prevention#
On JupyterHub, treat
jupyter-server-proxyas part of the standard environment.
Symptom: viewer loads but no events arrive (on_selection never fires)#
Symptom#
The viewer UI is interactive, but Python callbacks do not fire.
Likely causes (ordered)#
The iframe cannot reach the server’s
/_cellucid/eventsendpoint (proxy/mixed-content/firewall).Requests to
/_cellucid/eventsare blocked by an ad blocker / corporate proxy.You are interacting with a stale iframe (kernel restarted; viewerId no longer registered).
The selection payload is too large (hits the 1MB request limit).
How to confirm#
In Python, run:
viewer.debug_connection()
In browser devtools → Network:
look for requests to
/_cellucid/eventsconfirm status codes (should be 200 JSON)
Confirm you are using the right viewer object:
print
viewer.viewer_urland compare with the iframe URL (if visible).
Fix#
Connectivity/proxy issues:
Stale iframe:
re-run the cell that creates the viewer
Payload too large:
avoid selecting huge fractions of the dataset in one step
prefer session bundle capture for exporting large highlight membership sets
Prevention#
For large datasets, avoid “select everything”; build highlight groups in smaller steps.
Keep notebooks reproducible: re-run the viewer creation cell after kernel restart.
Symptom: events arrive, but indices don’t match my AnnData#
Symptom#
Python receives indices, but they refer to “the wrong cells” when you index adata[cells].
Likely causes#
You reordered/subset
adataafter creating the viewer.You are using a different
adataobject than the one you passed toshow_anndata.
How to confirm#
Print basic sanity checks:
event = viewer.wait_for_event("selection", timeout=None) cells = event["cells"] print(min(cells), max(cells), len(cells)) print("adata.n_obs:", adata.n_obs)
If
max(cells) >= adata.n_obs, you are definitely mismatched.
Fix#
Only interpret indices against the exact dataset the viewer is serving.
If you must reorder/subset, create a new viewer from the modified AnnData.
Prevention#
Treat the viewer’s dataset as immutable while you’re using hooks.
Symptom: Python commands don’t affect the UI (highlight/color/reset do nothing)#
Symptom#
Calling viewer.highlight_cells(...) / viewer.set_color_by(...) / viewer.reset_view() has no visible effect.
Likely causes (ordered)#
Viewer not displayed (commands are sent via notebook JS injection).
The iframe is stale or not the one tied to this Python viewer.
Connectivity/proxy issues prevent the iframe from fully initializing Jupyter mode.
You are offline with a cached UI build that doesn’t support the command (rare, but possible).
How to confirm#
If
viewer._displayedis False (notebooks): you didn’t display the viewer.Run:
viewer.wait_for_ready(timeout=60) viewer.debug_connection()
Fix#
Display the viewer:
viewer.display()
Re-run the viewer creation cell.
Clear UI cache and retry:
viewer.clear_web_cache()
Use Security: CORS, origins, and mixed content to fix proxy/mixed-content failures.
Prevention#
Always
viewer.wait_for_ready()before sending commands in scripts/notebooks.
Symptom: session bundle capture fails or hangs#
Symptom#
viewer.get_session_bundle() times out or raises.
Likely causes#
Iframe can’t reach
/_cellucid/session_bundle(connectivity/proxy).Pending request expired; server responds “No pending session bundle request”.
Bundle exceeds size cap (512MB).
How to confirm#
Browser devtools → Network:
look for
/_cellucid/session_bundle?viewerId=...&requestId=...check status code and response text
In Python, inspect the debug report for recent
session_bundleevents.
Fix#
Retry
viewer.get_session_bundle()(generates a fresh requestId).Reduce session size (fewer highlight groups / less state).
Fix connectivity issues (proxy/mixed content).
Symptom: “Port already in use” / ports keep incrementing#
Symptom#
You see the viewer using ports like 8765, 8766, 8767… and remote workflows break.
Likely causes#
You created multiple viewers and didn’t stop them.
You re-ran cells repeatedly without cleanup.
Fix#
Stop the viewer when done:
viewer.stop()
For remote/HPC workflows, choose a fixed port:
viewer = show_anndata("data.h5ad", port=8765)
Prevention#
Use a
try/finallypattern to stop viewers.
Debugging checklist (copy/paste)#
print("server_url:", viewer.server_url)
print("viewer_url:", viewer.viewer_url)
viewer.wait_for_ready(timeout=60)
report = viewer.debug_connection()
report
In a browser:
open
<server_url>/_cellucid/healthopen devtools → Network:
filter for
/_cellucid/eventsand/_cellucid/session_bundle
Next steps#
Reference (schemas + env vars): Reference (hooks, commands, schemas, endpoints)
Screenshot/diagram checklist for docs: Screenshots and diagrams checklist (hooks)