Skip to content

fix(runtimed): unblock headless conda/uv kernels stuck on AwaitingTrust#2760

Draft
rgbkrk wants to merge 3 commits into
mainfrom
fix-default-data-packages-numpy-scipy
Draft

fix(runtimed): unblock headless conda/uv kernels stuck on AwaitingTrust#2760
rgbkrk wants to merge 3 commits into
mainfrom
fix-default-data-packages-numpy-scipy

Conversation

@rgbkrk
Copy link
Copy Markdown
Member

@rgbkrk rgbkrk commented May 14, 2026

Found via headless gremlin runs against the latest nightly: any MCP create_notebook call that included a dep missing from the local allowlist parked the kernel at awaiting_trust forever. Headless callers (nightly gremlins, Claude Code) have no way to click through a trust dialog, so the notebook just sat there until the request timed out.

Three pieces of the fix.

Design decisions

Why numpy and scipy — both are foundational deps for any data persona, but neither was in the seed list. On my machine the gremlin's request for conda + [pandas, numpy, matplotlib] failed because the audit log showed every pypi:* user-approval over the past month but zero conda:* user-approvals — no one's ever clicked through a conda trust dialog, so conda:numpy never made it into the allowlist. Adding them to DEFAULT_DATA_PACKAGES seeds both ecosystems on daemon start. Pool envs already pulled numpy transitively via pandas/matplotlib, so the only meaningful install delta is scipy.

Why auto-trust on CreateNotebook deps — when an MCP tool call passes dependencies: ["pandas", ...] explicitly, the tool call itself is the consent event. The Claude Code UI (or whatever MCP host) approves the tool invocation; by the time bytes hit the daemon the caller has already opted in to those deps. OpenNotebook goes nowhere near this code — a downloaded notebook with a sketchy dep like canhazpassword still surfaces through the regular trust dialog.

Why a next_action hint instead of changing tool schemas — the existing manage_dependencies tool already accepts trust: true to grant approval. The gremlin didn't reach for it because the awaiting_trust response was a dead end. Surfacing the trust state + remediation hint in runtime.trust and runtime.next_action makes the available path visible to any caller looking at the response. Tool descriptions are untouched, so no tool-cache update needed.

Behavioral coverage

Scenario Trust path Outcome
create_notebook + explicit deps, no prior trust seed via mcp_create_notebook auto-launch
create_notebook + no deps NoDependencies short-circuit, no seed auto-launch
create_notebook + already-approved deps seed is a no-op (ON CONFLICT DO NOTHING) auto-launch
open_notebook of a downloaded .ipynb unchanged, hits trust dialog awaiting_trust until human approves
Any path that ends awaiting_trust unchanged kernel state response now includes runtime.trust + runtime.next_action pointing at manage_dependencies({trust: true})

Pool env impact

DEFAULT_DATA_PACKAGES participates in the pool-package hash (expected_pool_package_hash). The const change invalidates existing pool envs; next daemon start rebuilds them with the new list. numpy was already a transitive dep of pandas/matplotlib, so the practical install delta is scipy (~50MB per env).

Test plan

  • cargo check -p runtimed -p runt-mcp
  • cargo test -p runtimed --lib trusted_packages (10/10)
  • cargo test -p runtimed --lib daemon (100/100)
  • cargo test -p runtimed --lib notebook_sync_server (291/291, includes the two new auto-trust tests)
  • cargo test -p runt-mcp --lib (120/120)
  • Re-run the data-scientist gremlin against a daemon built from this branch and confirm conda + numpy launches without stalling.

Tests added

  • test_seed_trust_from_doc_metadata_promotes_to_trusted — populates the doc with explicit deps, calls the helper, verifies the trust check now resolves to Trusted.
  • test_seed_trust_from_doc_metadata_skips_when_no_deps — empty dep list short-circuits via NoDependencies and doesn't write phantom rows to the allowlist.

The MCP `create_notebook` + dep flow could park a kernel in
`awaiting_trust` indefinitely when any explicit dep was missing from the
local allowlist. Headless callers (the nteract MCP gremlins, Claude
Code) have no way to click through a trust dialog, so the notebook just
sat there until the request timed out.

Three pieces:

- `DEFAULT_DATA_PACKAGES` now includes `numpy` and `scipy`. They were
  the foundational deps the seed list missed; adding them closes the
  most common case for fresh installs against both the `pypi` and
  `conda` ecosystems. The pool-package hash includes the list, so
  existing pool envs invalidate and rebuild on next daemon start.

- `CreateNotebook` auto-seeds the allowlist with the deps it was
  handed. An MCP tool call that explicitly passes
  `dependencies: ["pandas", ...]` is itself the consent event — by
  the time bytes hit the daemon, the caller (or the Claude Code UI
  approving the tool call) has already opted in. Notebooks loaded from
  disk via `OpenNotebook` go nowhere near this code and still gate on
  the regular trust dialog, so a sketchy dep like `canhazpassword`
  pulled from a downloaded notebook is still surfaced for review.

- `runtime` info in MCP responses (`create_notebook`, `connect_notebook`,
  etc.) now carries the `trust` block from `RuntimeState.trust` and,
  when the kernel is parked at `AwaitingTrust`, a `next_action` hint
  pointing to `manage_dependencies({trust: true})`. Tool schemas are
  unchanged — only response shapes get richer.
@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog Bot commented May 14, 2026

Pullfrog  | Review this ➔

@github-actions github-actions Bot added the daemon runtimed daemon, kernel management, sync server label May 14, 2026
rgbkrk added 2 commits May 14, 2026 08:55
Codex review on #2760 caught that the seed helper was also running on
the session-restore branch (cell_count > 0 at start). In that case
the request's `dependencies` array is ignored, but the seed still
read the doc and would have approved whatever the restored notebook
declared — letting a CreateNotebook handshake silently grant trust to
a persisted dep list the caller never opted into.

Gate the seed on `freshly_created`: only true when
`create_empty_notebook` actually populated the doc from the request
deps. Restore paths fall back to the regular trust-dialog flow.
`test_config` was leaving `trusted_packages_db_path` at the
`DaemonConfig::default()` value, which points at the shared
`daemon_base_dir().join("trusted-packages.sqlite")`. Any test
that approved a dep (trust dialog OR the new `CreateNotebook`
auto-seed path) leaked rows across the whole suite. The
`test_untrusted_*` tests started false-passing on dev machines
and false-failing on CI depending on what ran before them.

Scope the db under each test's `TempDir`. Surfaces today as a
fix for the `test_untrusted_launch_and_sync_environment_are_daemon_rejected`
flake against the auto-trust seed, but the underlying gap predates
this PR.
@github-actions github-actions Bot added the test Test infrastructure and coverage label May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

daemon runtimed daemon, kernel management, sync server test Test infrastructure and coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant