Files
fn_registry/dev/issues/completed/0050-jupyter-exec-collab-client-failure.md
T

209 lines
7.2 KiB
Markdown

---
id: "0050"
title: "`jupyter_exec` falla por cliente colaborativo (RESUELTO 2026-05-05)"
status: completado
type: bugfix
domain: []
scope: multi-app
priority: media
depends: []
blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
---
# 0050 — `jupyter_exec` falla por cliente colaborativo (RESUELTO 2026-05-05)
## Cierre (2026-05-05)
Resuelto via opcion (b) del propio issue: migrar `jupyter_exec` a REST + `KernelClient`
clasico, bypassando `NbModelClient`/Y.js.
Bug raiz adicional encontrado al reproducir: `_notebook_exists` usaba `HEAD /api/contents`,
y Jupyter Server responde **405 Method Not Allowed** (no soporta HEAD ahi). Cambiado a
`GET /api/contents?content=0`.
Cambios:
- `python/functions/notebook/jupyter_exec.py` reescrito (v2.0.0). Sync, sin asyncio.
Append/cell ahora usan REST `/api/contents` para leer/escribir celdas + outputs y
`KernelClient` para ejecutar.
- `python/functions/notebook/tests/test_jupyter_exec.py` con 5 tests unitarios
(incluye guard para que HEAD no vuelva) y 4 tests e2e que arrancan un Jupyter Lab
en puerto libre con `--collaborative` y verifican los tres modos.
- 9/9 tests pasan en local.
Trade-off documentado en el `.md`: los cambios se persisten a disco; Jupyter Lab los
detecta y muestra en el browser (puede pedir 'Revert to disk' segun version y
conflictos). Esto basta para los analyses del proyecto y es lo que ya se hacia con el
workaround `nbformat`+`nbconvert`.
---
# 0050 — `jupyter_exec` falla por cliente colaborativo (workaround documentado)
## APP Metadata
| Campo | Valor |
|-------|-------|
| **ID** | 0050 |
| **Estado** | pendiente |
| **Prioridad** | media |
| **Tipo** | bug — `python/functions/notebook/jupyter_exec.py` |
## Dependencias
Ninguna. Independiente del resto.
---
## Sintoma
Al ejecutar `jupyter_exec.py append <notebook> <code>` contra un Jupyter Lab
arrancado con el launcher estandar de los analyses (`run-jupyter-lab.sh`,
flag `--collaborative`), la operacion falla con:
```
{"error": "HTTP Error 405: Method Not Allowed"}
```
`jupyter_write.py append-code` y `append-markdown` SI funcionan (no usan el
canal colaborativo). El bug solo afecta a `jupyter_exec`, que necesita
ejecutar la celda en el kernel y para eso usa `jupyter_nbmodel_client`
con websocket Y.js.
Reproducido en `2026-05-04` durante la construccion del analysis
`projects/osint_graph/analysis/gliner_glirel_tuning/`. El resto de
funciones del modulo `notebook/` quedan intactas:
```bash
$JX append <nb> <code> # ❌ HTTP 405
$JW append-code <nb> <code> # ✅ OK (sin ejecucion)
$JW append-markdown <nb> <md> # ✅ OK
$JX cell <nb> <idx> # 🔁 No probado, pero usa el mismo cliente
$JX kernel <code> # 🔁 No probado
```
---
## Diagnostico (parcial)
`jupyter_nbmodel_client` espera que el server tenga la extension
`jupyter_collaboration` activa y montada en `/api/collaboration/...`. El
launcher arranca jupyter con el flag CLI `--collaborative`, que en
versiones recientes (`jupyter_server >= 2.x`, `jupyter-collaboration >= 4.x`)
**ya no es suficiente** — la extension se carga via entry-point y se
controla con flags distintos (`--YDocExtension.disable_rtc` o equivalente),
o requiere un fichero de config explicito.
Salida de `jupyter_discover.py` confirma el sintoma indirectamente:
```json
{ "url": "http://localhost:8888", "collaborative": false, ... }
```
aunque `--collaborative` esta en el launch command. Es decir: el server
arranca, expone la API REST, pero la capa colaborativa NO esta activa.
---
## Workaround usado en `gliner_glirel_tuning`
Cambio de tactica: en lugar de construir el notebook con `jupyter_exec
append` celda a celda, **se ejecutan los experimentos en un script
externo** y se empotran las celdas (codigo + outputs ya generados) con
`nbformat` directo a fichero. El notebook resultante es persistente y
no necesita el canal colaborativo.
```python
# build_notebook.py
import nbformat as nbf
nb = nbf.v4.new_notebook()
for src, stdout in cells:
cell = nbf.v4.new_code_cell(src)
cell.outputs = [nbf.v4.new_output("stream", name="stdout", text=stdout)]
nb.cells.append(cell)
nbf.write(nb, "notebooks/01_foo.ipynb")
```
Si se quieren outputs reales (DataFrames como HTML, figuras matplotlib),
ejecutar despues con `nbconvert`:
```bash
IPYTHONDIR=$(pwd)/.ipython ./.venv/bin/jupyter nbconvert \
--to notebook --execute notebooks/01_foo.ipynb \
--output 01_foo.ipynb --ExecutePreprocessor.timeout=300
```
Esto bypassa completamente el canal colaborativo y produce un `.ipynb`
funcional, abrible en Jupyter Lab para ver / iterar / re-ejecutar.
Ver `projects/osint_graph/analysis/gliner_glirel_tuning/build_notebook.py`
y `build_notebook_e2e.py` para ejemplos vivos.
---
## Causas raiz a investigar
1. **Verificar la version de `jupyter-collaboration`** en el venv del
analysis. Si es >=4.x, el flag `--collaborative` ya no aplica y el
launcher (`write_jupyter_launcher_bash_io`) tiene que actualizarse.
2. **El cliente** `jupyter_nbmodel_client` puede tener su propia
ventana de versiones soportadas — comprobar pinning en
`python/.venv` y en los venvs de analyses.
3. **El endpoint** `/api/collaboration/document` debe responder a un
`GET` con HTTP 200 cuando la extension esta activa. Si responde
`405`, el cliente intenta una operacion (POST/PUT) sobre un endpoint
que solo acepta GET, sintoma de mismatch.
---
## Tareas
1. Reproducir el `HTTP 405` con un notebook nuevo y un kernel nuevo
en un analysis recien creado.
2. Capturar la URL exacta y el metodo HTTP que dispara el 405
(anadir logging a `jupyter_exec.py` linea ~192/229 donde llama a
`get_jupyter_notebook_websocket_url`).
3. Verificar version de `jupyter-collaboration` en el venv y comparar
con la matriz de compatibilidad de `jupyter_nbmodel_client`.
4. Una de dos:
- **(a)** Corregir el flag/config en `write_jupyter_launcher_bash_io`
para activar correctamente la colaboracion en versiones nuevas.
- **(b)** Si la API colaborativa cambio mucho, **migrar
`jupyter_exec.py` a usar el `JupyterClient` clasico** (REST + WebSocket
directo al kernel sin Y.js) que es estable a traves de versiones.
`jupyter_kernel.py` ya hace algo asi y funciona.
5. Anadir un test e2e basico en `tests/` que arranca jupyter, lanza
`jupyter_exec append`, verifica que la celda se ejecuto y captura
stdout. Sin esto el bug puede regresar.
---
## Out of scope
- Reescribir el sistema completo de notebook collaboration.
- Migrar a un MCP. La regla `notebook_collaboration.md` es explicita:
estas funciones reemplazan al MCP jupyter.
---
## Riesgos
- Si la causa es la matriz de versiones, la opcion (a) puede generar
fricion futura cada vez que `jupyter-collaboration` haga un breaking
change. La opcion (b) es mas robusta a largo plazo aunque pierde la
capacidad de ver cambios en tiempo real desde el navegador.
## Notas operativas
Mientras este bug exista, el patron recomendado para construir notebooks
desde un agente Claude en un analysis es:
1. `build_notebook.py` con `nbformat` para estructura + outputs estaticos.
2. `nbconvert --execute` para outputs reales (HTML, plots).
3. Si necesitas tiempo real con el browser, abre el notebook ya generado
en Jupyter Lab y reejecuta a mano.
El propio analysis `gliner_glirel_tuning` es referencia.