feat(deepnote): load integrations from .deepnote.env.yaml and .env - #440
feat(deepnote): load integrations from .deepnote.env.yaml and .env#440tkislan wants to merge 155 commits into
Conversation
…d) + add project-id resolver
Chunk 1 of single-notebook migration (§4 partial, §5). No behaviour change.
- Manager caches originals in a nested Map<projectId, Map<notebookId, project>>
so sibling files sharing a project.id no longer clobber each other.
- New API: getOriginalProject(projectId, notebookId) exact/no-fallback,
getAnyProjectEntry(projectId), storeOriginalProject/updateOriginalProject
(3-arg), updateProjectIntegrations iterates all entries.
- Update IDeepnoteNotebookManager and IPlatformDeepnoteNotebookManager; repoint
all project-level read-only callers to getAnyProjectEntry.
- Add canonical readDeepnoteProjectFile and resolveProjectIdFor{File,Notebook}.
- Selection state and init-run tracking intentionally kept (removed in later chunks).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…rop selection machinery Chunk 2 of single-notebook migration (§1 + Cleanup). - deserializeNotebook renders the first non-init notebook (findDefaultNotebook), falling back to the only/init notebook; never composes init. - serializeNotebook resolves the target from document metadata alone (projectId + notebookId required) and looks it up with the exact getOriginalProject, throwing clear errors instead of falling back to a wrong sibling. - detectContentChanges collapses to a single-notebook comparison. - Remove the ?notebook=<id> selection machinery: findCurrentNotebookId, the manager's selection state + interface methods, the explorer's query-param opens and selectNotebookForProject calls, and the tree item's custom resourceUri. - Explorer no longer depends on IDeepnoteNotebookManager. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…k siblings Chunk 3 of single-notebook migration (§0, §2, §3). - Add allocateSiblingUri: the single filesystem-aware, collision-safe sibling filename allocator (bumps -2/-3 before .deepnote, honors an in-batch reserved set, bounded retries). - Add a notebook file factory (buildSingleNotebookFile / buildSiblingNotebookFileUri) for creating sibling single-notebook files (wired into the explorer in a later chunk). - Add DeepnoteMultiNotebookSplitter: on opening a multi-notebook .deepnote file, offer to split it into one new single-notebook file per notebook. The action flushes the editor if dirty, writes all children, migrates the environment selection, then closes the tab and deletes the original to trash. A child-write failure leaves the original intact (write-before-delete). - Wire the splitter into activation with an optional (desktop-only) environment mapper; add a refresh() passthrough on the explorer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
Chunk 4 of single-notebook migration (§6). - Add DeepnoteProjectMetadataPropagator (desktop): given a project id and a project-level mutator, enumerate every sibling .deepnote file on disk (open or closed), apply the change, and write it back. Skips no-op writes, refreshes the manager cache for open siblings, and collects per-file failures instead of aborting. Fires an onFileWritten hook so the file watcher treats each write as a self-write (no reload/save storm). - Route integration updates and project rename through the propagator so closed siblings stay consistent; web falls back to the cache-only / single-file paths. - Expose getOriginalProject/updateOriginalProject on the platform manager interface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…us bar Chunk 5 of single-notebook migration (§7). - Tree is grouped: ProjectGroup (by project id) -> ProjectFile -> Notebook. A single-notebook file is a leaf labelled with its notebook; legacy multi-notebook files stay collapsible. The init notebook is excluded from counts everywhere. - Refresh is grouping-safe: refreshNotebook evicts every sibling cache entry for a project id and all refreshes fire a full-tree change (no per-item fires). - Commands are project-scoped vs notebook-scoped; new/duplicate/add-notebook create sibling files via the factory (never appended), delete removes the file for a single-notebook file, and notebook names are unique within a project group. - Add a status bar item showing the active Deepnote notebook with a "Copy Active Deepnote Notebook Details" command. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
Chunk 6 of single-notebook migration (§8). - Key the server starter maps, the config handle, and the kernel auto-selector by notebook.uri.toString() - the same identity the kernel and controller use - so a notebook's server is 1:1 with its kernel. Sibling notebooks of one project no longer share a server; the working directory and SQL env are taken from each notebook's own file. - Fix environment deletion: stop every server using the environment (including closed notebooks whose server is still running) before removing the mappings, driven from the notebook->environment mapper. Drop the dead environmentServers map that was never populated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…le reader Chunk 7a of single-notebook migration (§9). - Write snapshots with notebook-scoped filenames via @deepnote/convert (generateSnapshotFilename / parseSnapshotFilename), replacing the local slug and filename regex. - readSnapshot resolves snapshots path-free (it runs at deserialize, which has no URI): glob by project id, rank the notebook-scoped match first and keep legacy project-scoped snapshots as a fallback, and skip an empty-output "latest" (save race) or a corrupt file while walking candidates. Legacy snapshots are read, never migrated or deleted. - Defer the execution snapshot save until outputs settle (quiet window with a max wait) and cancel it on re-execute / close. - Use convert's computeSnapshotHash on the save path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
Chunk 7b of single-notebook migration (§10). - The init runner now subscribes to kernel start and restart events and runs the init notebook found in its own sibling .deepnote file (matched by project id + initNotebookId via isValidSiblingInitCandidate), instead of looking it up in the main file's notebooks. - Track "init has run" per kernel in a WeakSet<IKernel>: a fresh kernel runs init once, and an in-place restart (which fires onDidRestartKernel) re-runs it so the kernel is re-initialized before the next user cell. A missing sibling is logged and skipped without permanently marking the project. - Remove the manager's persistent init-run tracking and the selector's init staging; the runner owns init triggering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
- void the fire-and-forget onExecutionComplete call (no-floating-promises), matching the existing void performSnapshotSave pattern. - Use American "behavior" in a comment. - Add test-only technical words (basenames, initmain, Résumé, unparseable) to cspell. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…of duplicating it The mocha ESM loader wholesale-mocked @deepnote/convert and reimplemented its pure helpers (resolveSnapshotNotebookId, splitByNotebooks, isValidSiblingInitCandidate, snapshot filename generate/parse, hashing, etc.). That duplicated upstream logic with no drift detection: if convert changed, the mock silently kept the old behavior and tests stayed green against a fiction. - Remove the @deepnote/convert interception from build/mocha-esm-loader.js so unit tests exercise the real package's pure functions (and now track its actual API). - Mock only the one genuinely side-effecting export, convertIpynbFilesToDeepnoteFile (real node:fs I/O), via esmock in the explorer import suites where it is used. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…r paths From the Codex review of the PR (F1/F3/F4/F5), all independently verified: - F1 (P1): snapshot save fetched the cached project with getAnyProjectEntry(projectId), which can return the wrong sibling when multiple single-notebook siblings of one project are open, silently skipping the snapshot write. Use the exact getOriginalProject(projectId, notebookId) lookup instead. - F3: collectNotebookNamesForProject globbed **/*.deepnote without skipping snapshot sidecars, so stale snapshot notebook names polluted the name-uniqueness set. Filter snapshot files (matching the tree provider and propagator). - F4: detectContentChanges compared notebooks[0]; for a legacy [init, main] file the edited notebook is not at index 0, so edits were missed and modifiedAt preserved. Match the notebook by id. - F5: the deferred-save timer fired performSnapshotSave as a floating promise; wrap the save body in try/catch/finally so a build/write failure is logged (not an unhandled rejection) and execution state is always cleared. Adds regression tests for F1 (exact lookup), F3 (snapshot exclusion), and F4 (match by id). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…el init runs Addresses round-2 code-review findings G2 and G3 (both verified P2). - G2: deepnoteFileChangeWatcher's snapshot block-id recovery used the project-only getAnyProjectEntry(projectId), which can return a different open sibling's cached project (siblings share project.id), leaving originalBlocks undefined and silently skipping recovered outputs. Use the exact getOriginalProject(projectId, notebookId) — the same fix already applied to snapshotService (F1), here in the watcher path that was missed. - G3: moving init execution to the event-driven runner dropped the notebook-close cancellation that the kernel auto-selector used to provide, so closing a notebook mid-init left the remaining init blocks executing against a closed notebook. Tie the init run to a CancellationTokenSource cancelled on notebook close and dispose it in a finally. Adds regression tests for both (each fails on the pre-fix code). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…ort/delete Removes two pieces of functionality; also folds in the branch's in-progress updates this work was layered on top of (they could not be isolated, as the removals are interleaved with and built on top of that WIP). Removed - project-metadata propagator: - Delete DeepnoteProjectMetadataPropagator and its types, drop the DI binding, and unwire it everywhere (activation, file-change watcher self-write hook, integration webview, explorer rename). Project-level fields are no longer fanned out across sibling files: each notebook owns its own integrations, and project-name drift is accepted for now. Drop the now-dead updateOriginalProject manager method and two stale comments. Removed - project-level explorer commands: - Delete the exportProject and deleteProject commands (constants, command-arg type, registrations, package.json command defs + sidebar menus, nls titles, and their unit tests). Per-notebook export remains via the existing exportNotebook command (first non-init notebook of the file). Also includes the branch's pending updates the above was built on: dependency bumps (incl. @deepnote/convert 4.0), the getOriginalProject -> getProjectForNotebook manager rename and getAnyProjectEntry removal, and assorted snapshot/serializer/kernel adjustments. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Brings in #432 (Cloud SQL integration support). Resolved the package.json and package-lock.json conflicts by keeping this branch's newer @deepnote/* versions (blocks 4.6.0, convert 4.0.0, runtime-core 0.4.0); @deepnote/database-integrations is 1.5.0 on both sides, and the Cloud SQL source from #432 merged cleanly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
@deepnote/blocks@4.6.0+ renders `text-cell-bullet` blocks with `indent_level >= 1` using leading spaces (two per level) before the bullet marker. stripMarkdown's bullet regex only matches at column 0, so the leading indentation must be trimmed first for the plain-text cell value to round-trip correctly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
…es re-exports snapshotFiles.ts re-exported six snapshot-filename helpers from @deepnote/convert. Remove the re-export block and import the helpers directly from @deepnote/convert at each use site (snapshotService.ts and the snapshotFiles unit test). snapshotFiles.ts now keeps only its local helpers (SNAPSHOT_FILE_SUFFIX, isSnapshotFile, extractProjectIdFromSnapshotUri) plus the single internal use of parseSnapshotFilename. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Refactor the buildSnapshotPath method to accept an object as an argument, improving readability and maintainability. Update all relevant calls to this method throughout the snapshotService and its unit tests to match the new signature. This change enhances the clarity of parameter usage and reduces the risk of errors when passing arguments.
Trim the single-notebook test suites by removing duplicate and tautological tests and collapsing/merging several others, shrinking the PR's test additions by ~640 lines with no loss of real coverage. Cuts target only tests this branch added: - exact-(projectId, notebookId)-lookup restatements duplicated across the watcher, serializer, snapshot, and manager suites - wrapper tests already covered by the delegate's own tests (addNotebookToProject, sibling-file allocation, project-id resolution) - tautologies over trivial template/getter functions (serverUtils) - framework-registration smoke tests (status bar) Merges keep the one meaningful assertion and drop the duplicate scaffolding (e.g. legacy-delete no-op folded into the existing delete test; two init builders parametrized into one). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
When splitting a legacy multi-notebook .deepnote file into single-notebook
siblings, rename the original to `<name>.deepnote.legacy` instead of moving it
to the OS trash. The `.legacy` suffix takes it out of the extension's view (it
no longer matches `*.deepnote`) while keeping it on disk next to the split
results, so the user can restore it by removing the suffix.
Unlike `workspace.fs.delete({ useTrash: true })`, this is deterministic and does
not depend on an OS trash backend (which can be absent on headless Linux).
Collisions bump the name to `.legacy-2`, `.legacy-3`, … and the rename still
happens only after every child is durably written (write-before-retire).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Add an ExTester end-to-end test that drives the real VS Code UI through the on-open split of a legacy multi-notebook .deepnote file: it asserts the split prompt, the one-file-per-notebook result, the retained `.legacy` backup, that each sibling opens without re-prompting, and that content plus the project integration fan out into every split file. Add a `createScreenshotter(this)` helper that captures step screenshots into a per-spec directory derived from the running test file (`test/e2e/screenshots/<spec>/`), plus the `sales-analytics.deepnote` fixture. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
…ites Add ExTester end-to-end suites covering: - opening a plain single-notebook file (opens directly, no split prompt, the status bar shows the notebook name); - splitting a multi-notebook file that declares an init notebook (the init notebook becomes its own single-notebook sibling; each main sibling still references it via initNotebookId); - the init-notebook runner: the sibling init notebook runs hidden in a main notebook's kernel so its definitions are available, and re-runs after a kernel restart. Add the quick-notes and etl-pipeline fixtures (including the pre-split extract/init siblings), and disable the kernel-restart confirmation in the E2E settings so the restart test can drive it non-interactively. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Add an ExTester end-to-end test asserting the Deepnote Explorer groups sibling
.deepnote files by project: three files sharing one project.id collapse into a
single "Marketing" group ("3 files") whose leaves are the three notebooks, while
a file from a different project appears as its own group. Reads the tree by
diffing visible leaves before/after expanding (avoids the page-object library's
flaky CustomTreeItem.getChildItems). Adds the three marketing fixtures.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
When several E2E suites run in one ExTester session (as in CI, via the `*.e2e.test.js` glob), every workspace-folder open after the first failed with "Failed to open folder after 5 attempts" in the suite's `before all` hook — only the alphabetically-first suite passed. Root cause: the simple "Open Folder" dialog (files.simpleDialog.enable) navigates one directory level *toward* the typed path per OK click and only accepts the folder once the browser is AT it. The helper clicked OK once then re-opened the dialog each attempt, which reset navigation back to the default directory — for the 2nd+ open that default is the previous, now-deleted workspace, so the dialog fell back to "/" and never converged on the target. Fix: click OK repeatedly within a single dialog until the pre-open workbench element detaches (reload = folder accepted), instead of re-opening per attempt; and set `window.openFoldersInNewWindow: "off"` so "Open Folder" reuses the current window, keeping that reload detectable. Verified with four suites (16 tests) opening four folders in one session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Add an ExTester end-to-end test for the Deepnote status-bar item: it shows the active notebook's name (with the "Copy Active Deepnote Notebook Details" tooltip), hides when a non-notebook editor is focused, and — on click — copies the notebook details to the clipboard with a confirmation toast. The clipboard is verified by pasting into a scratch text file and reading it back. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Add an ExTester end-to-end test for the notebook-management commands that create and rename sibling .deepnote files from the Deepnote explorer: New Notebook, Add Notebook (project-group context menu), Duplicate Notebook, and Rename Notebook — each verified by the resulting notebook name inside the sibling files plus the confirmation toast. Delete Notebook is included as a pending test: its context-menu -> native confirmation-modal interaction is unreliable to drive under ExTester (documented inline), so it is left as a manual check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Add an ExTester end-to-end test for the Deepnote integrations UI: opening "Manage Integrations" for a notebook whose project declares an integration lists it (the "Sales BigQuery" integration on the sales-analytics-revenue fixture), while a plain notebook (quick-notes) shows no such integration. Adds the sales-analytics-revenue fixture (a single-notebook split of the Sales Analytics project carrying the BigQuery integration + its SQL cell). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Add an ExTester end-to-end test for renaming a Deepnote project from the Explorer: none of the three "Marketing" siblings is opened, then the project group is renamed to "Growth" via its context menu; the new name is asserted to fan out to every sibling .deepnote file on disk (and the old name gone), with the Explorer group relabelled and a confirmation toast. Extract the shared Deepnote tree helpers (getDeepnoteExplorerSection, readDeepnoteTreeRows, findDeepnoteGroup/Leaf, selectDeepnoteContextMenu) into test/e2e/helpers/deepnoteTree.ts for reuse across the tree-driven suites. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Add an ExTester end-to-end test for the edge case where a .deepnote file's only notebook is its init notebook. Opening bootstrap-only.deepnote renders that notebook as a fallback (status bar shows "Bootstrap"), does not raise the split prompt (it is a single-notebook file), and the Explorer shows it with "0 notebooks" (the init notebook is excluded from the count). Adds the bootstrap-only fixture. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
|
@tkislan I converted this one to draft as there are quite a lot of conflicts to address. |
Conflicts all came from #375 (PostHog telemetry), which touched the same integration files this branch reworks. Combined both sides: - integrationTypes: keep the branch's isConfigurableDatabaseIntegrationType and DetectedIntegration rename, add main's toTelemetryIntegrationType. IntegrationStatus stays deleted — no caller survives on either side. - federatedAuthCommandHandler: authenticate() keeps the required `resource` and now returns CommandOutcome; the new no-resource guard reports 'failed'. - integrationWebview: save/reset/deleteConfiguration return boolean for the telemetry gate, and a file-configured refusal returns false so no event fires for an edit that never happened. The authenticate command call keeps the activeFileUri argument and captures the outcome. - sqlCellStatusBarProvider: both new constructor dependencies; the switch_sql_integration event reuses the selectedIntegration the roster reconciliation already resolves (git had merged in a duplicate const). - Tests: the "authenticate" telemetry stub needed a three-argument matcher, since the command call now carries the resource — ts-mockito matches on argument count, so the two-argument stub silently returned nothing and the 'cancelled' case reported 'failed' (verified by mutation). typecheck 0, lint 0, spell-check 0, compile-tsc 0. Unit suite: 2589 passing, 234 pending, 0 failing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnG25e9FTNvEmxxxKDGViM
This PR shipped four new user-facing surfaces with no telemetry. Three of them
are now visible; the two that can fail silently are covered.
refresh_integration_env — the no-restart live refresh had no way to report that
it failed. The user gets a status-bar message on success and nothing at all when
the toolkit snippet errors or executeHidden rejects, so a kernel left on stale
credentials was unobservable. refreshNotebook's boolean becomes a four-way
result, which the three existing failure paths already distinguished; the counts
and failureKind fall out of it. Skipping every notebook emits nothing, so
editing an env file with no kernel running is not a per-save emitter. No
outcome: there is no progress UI and no cancel, so 'cancelled' is unreachable
and a partial pass has no defensible single value.
switch_sql_integration.fromEnvFile — the picker now offers integrations declared
only in .deepnote.env.yaml, and picking one reconciles it into the project
roster. The existing event could not tell that apart from an ordinary switch.
The flag is hoisted from the reconciliation condition rather than re-derived, so
the two cannot drift. It counts FIRST picks: reconciliation writes the
integration into the roster, so the same pick later reports false.
integration_endpoint_failed — the loopback credential endpoint is always-on
infrastructure whose failure breaks all SQL. phase:'running' is the load-bearing
half; a mid-session death is otherwise invisible, since already-spawned kernels
never re-report. Emitted before the notification, not in its `then`: an
unattended notification never resolves.
integration_endpoint_failed is deliberately noun-first, unlike every existing
name in the union. Those are all user commands; this is a system failure the
user is shown, and forcing a verb ('report_...') would read worse.
Each assertion was observed failing against a mutated build before being kept:
the emit moved into the notification's then (kills the 'running' case, and only
that one), the skip guard removed, failureKind collapsed to a constant, and
fromEnvFile inverted.
typecheck 0, lint 0, spell-check 0, compile-tsc 0.
Unit suite: 2593 passing, 234 pending, 0 failing (baseline 2589, +4 new).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnG25e9FTNvEmxxxKDGViM
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/notebooks/deepnote/integrations/userpodApiEndpoints.node.ts (1)
124-156: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftDo not merge credentials across notebooks.
The endpoint authenticates only a project ID, then merges variables from every open notebook in that project. If two notebooks have different
.deepnote.env.yamlvalues, a kernel can receive credentials selected from another notebook by URI sort order.Bind the endpoint token and request identity to one notebook URI. Resolve variables only for that notebook. This preserves the PR’s notebook-scoped configuration contract.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/integrations/userpodApiEndpoints.node.ts` around lines 124 - 156, Replace the multi-notebook filtering, sorting, and merging in the Userpod API endpoint with notebook-URI-scoped authentication and request identity. Require the endpoint token/request to identify one notebook URI, validate that it belongs to the requested project and is open, then call getEnvironmentVariables only for that notebook and return its variables without merging credentials from other notebooks.src/notebooks/deepnote/integrations/integrationsEnvFileWatcher.node.ts (1)
83-91: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winRefresh after
.deepnote.env.yamldeletion.Line 90 suppresses refreshes after the file is deleted. The file no longer exists when
hasIntegrationsFile()runs. Running kernels then retain credentials from the deleted configuration.Track whether the event came from
.deepnote.env.yaml. Refresh affected notebooks for that event even when the file no longer exists. Keep the existence check for.env-only events. Add a deletion regression test.Also applies to: 119-133, 143-151
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/integrations/integrationsEnvFileWatcher.node.ts` around lines 83 - 91, The watcher logic around hasIntegrationsFile must distinguish .deepnote.env.yaml events from .env-only events: always add the notebook for integration-file changes, including deletion, while retaining the candidate-directory existence check for .env changes. Update the related event-handling branches around the affected notebook collection and add a regression test covering deletion-triggered refresh.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/notebooks/deepnote/integrations/integrationsEnvFileWatcher.node.ts`:
- Around line 83-91: The watcher logic around hasIntegrationsFile must
distinguish .deepnote.env.yaml events from .env-only events: always add the
notebook for integration-file changes, including deletion, while retaining the
candidate-directory existence check for .env changes. Update the related
event-handling branches around the affected notebook collection and add a
regression test covering deletion-triggered refresh.
In `@src/notebooks/deepnote/integrations/userpodApiEndpoints.node.ts`:
- Around line 124-156: Replace the multi-notebook filtering, sorting, and
merging in the Userpod API endpoint with notebook-URI-scoped authentication and
request identity. Require the endpoint token/request to identify one notebook
URI, validate that it belongs to the requested project and is open, then call
getEnvironmentVariables only for that notebook and return its variables without
merging credentials from other notebooks.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d601abdb-b4fb-4c0f-9adf-062542b88405
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (19)
package.jsonsrc/notebooks/deepnote/integrations/federatedAuth/federatedAuthCommandHandler.node.tssrc/notebooks/deepnote/integrations/federatedAuth/federatedAuthCommandHandler.node.unit.test.tssrc/notebooks/deepnote/integrations/integrationEnvLiveRefresher.node.tssrc/notebooks/deepnote/integrations/integrationEnvLiveRefresher.node.unit.test.tssrc/notebooks/deepnote/integrations/integrationEnvRefreshHandler.tssrc/notebooks/deepnote/integrations/integrationEnvRefreshHandler.unit.test.tssrc/notebooks/deepnote/integrations/integrationWebview.tssrc/notebooks/deepnote/integrations/integrationWebview.unit.test.tssrc/notebooks/deepnote/integrations/integrationsEnvFileWatcher.node.tssrc/notebooks/deepnote/integrations/integrationsEnvFileWatcher.node.unit.test.tssrc/notebooks/deepnote/integrations/types.tssrc/notebooks/deepnote/integrations/userpodApiEndpoints.node.tssrc/notebooks/deepnote/integrations/userpodApiEndpoints.node.unit.test.tssrc/notebooks/deepnote/sqlCellStatusBarProvider.tssrc/notebooks/deepnote/sqlCellStatusBarProvider.unit.test.tssrc/notebooks/serviceRegistry.node.tssrc/platform/analytics/types.tssrc/platform/notebooks/deepnote/integrationTypes.ts
🚧 Files skipped from review as they are similar to previous changes (8)
- src/notebooks/deepnote/integrations/integrationEnvRefreshHandler.unit.test.ts
- package.json
- src/notebooks/deepnote/sqlCellStatusBarProvider.ts
- src/notebooks/deepnote/integrations/integrationsEnvFileWatcher.node.unit.test.ts
- src/platform/notebooks/deepnote/integrationTypes.ts
- src/notebooks/deepnote/integrations/federatedAuth/federatedAuthCommandHandler.node.ts
- src/notebooks/deepnote/sqlCellStatusBarProvider.unit.test.ts
- src/notebooks/deepnote/integrations/integrationWebview.unit.test.ts
|
@dinohamzic resolved |
dinohamzic
left a comment
There was a problem hiding this comment.
@tkislan Sol Ultra, I'll test manually next:
-
[P1] Scope credential responses to one notebook. userpodApiEndpoints.node.ts authenticates only by project, then merges credentials from every open notebook sharing that project ID. Two checkouts with different YAML files can receive each other’s credentials or connect to the lexically first notebook’s database.
-
[P1] Refresh when file credentials cease to apply. integrationsEnvFileWatcher.node.ts checks whether YAML still exists after deletion, so deleting the last applicable file suppresses the refresh and leaves its
SQL_*variables live. The same happens when disabling the new setting because activation has no configuration-change listener. -
[P1] Reject IDs that normalize to the same environment variable. File-only configs are appended by exact ID at sqlIntegrationEnvironmentVariablesProvider.ts, but
prod-dbandprod_dbboth becomeSQL_PROD_DB; Object.fromEntries silently keeps the latter. A cell targetingprod-dbcan therefore execute against the wrong database. -
[P1] Scope OAuth tokens consistently with notebook-scoped configs. federatedAuthSqlBlockCodeGenerator.node.ts resolves OAuth metadata per notebook, while token storage remains keyed only by integration ID. Two notebooks using
bigquerywith different OAuth metadata continually overwrite or delete each other’s token. -
[P1] Display the same file-wins integration that execution uses. sqlCellStatusBarProvider.ts consults merged/file configuration only when no SecretStorage config exists. If YAML overrides stored “Staging” with “Production,” execution uses Production while the status bar still says Staging.
-
[P2] Preserve the selected YAML directory. integrationsFileConfigProvider.node.ts finds YAML and
.envindependently, so workspace-root YAML can accidentally consume a nested notebook’s.envrather than its sibling. Losing that source directory also leads certificate paths to be generated as/.deepnote/<id>/<certificate>. -
[P2] Complete the file-backed OAuth lifecycle. IntegrationItem.tsx offers Authenticate for file integrations but hides reset/delete, while the backend rejects those operations. There is consequently no supported way to clear a long-lived refresh token. Additionally, deriveTokenStatus checks only token existence, so edited OAuth metadata remains falsely “Authenticated” until execution fails.
-
[P2] Reconfigure the shared SQL LSP for each notebook/config change. deepnoteLspClientManager.node.ts snapshots file connections during client creation, while ensureSharedSqlClient reuses that client unchanged. File-backed autocomplete therefore reflects only the first notebook that starts the shared client.
-
[P2] Do not restart stopped kernels during refresh. integrationEnvLiveRefresher.node.ts gates only on
startedAtLeastOnce; after shutdown that remains true, and executeHidden unconditionally callskernel.start(). Editing credentials can silently relaunch a kernel the user stopped.
…config Addresses six findings from @dinohamzic's review of #440, each with a regression test seen failing against the pre-fix code. Refresh when file credentials cease to apply: the watcher re-probed the filesystem after its debounce, so deleting the last .deepnote.env.yaml suppressed the refresh and left its SQL_* variables live in the kernel. Track the changed file name per directory so the YAML's own events bypass the existence probe, and add an onDidChangeConfiguration listener so toggling deepnote.integrations.envFile.enabled also refreshes. Display the integration execution uses: the SQL status bar resolved SecretStorage first while execution resolves the file config first, so a YAML override could show "Staging" for a cell running against production. Resolve merged-first with SecretStorage as the fallback for ids the merge does not cover. Preserve the YAML's directory: the .env was located by an independent scan, so a workspace-root YAML could consume a nested notebook's .env. Scan from the YAML's own directory onward, keeping the root fallback. Anchor CA certificate paths: projectRootDirectory was an empty string, so sslrootcert resolved to /.deepnote/<id>/<cert>. A caCertificateName flips sslmode to verify-ca, so the connection failed rather than degrading. Complete the file-backed OAuth lifecycle: a file-configured integration offered Authenticate but no way to clear the resulting refresh token. Add a token-only Sign out, permitted for file-configured rows since the token store has no file layer to be overridden by. deriveTokenStatus now compares the stored fingerprint against current metadata instead of reporting any existing token as authenticated. Do not evict another notebook's OAuth token: a fingerprint mismatch means "unusable here", not "dead" - a sibling project can declare the same id with its own client. Dropping it destroyed that notebook's credential and restarted its kernel on every cell run. Reconfigure the shared SQL LSP: connections were snapshotted at client creation and the client reused unchanged, so completions reflected only the notebook that started it. Apply this notebook's connections on reuse. Do not relaunch dead kernels: the live refresher gated on startedAtLeastOnce, which never resets, while executeHidden calls kernel.start() unconditionally - so editing credentials relaunched a kernel that had since died. Gate on a live session instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnG25e9FTNvEmxxxKDGViM
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/notebooks/deepnote/integrations/integrationWebview.ts (1)
548-557: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winTwo new methods were inserted between an existing JSDoc block and the method it documents. In both places the pre-existing doc now attaches to the new method, and the originally documented method is left undocumented.
src/notebooks/deepnote/integrations/integrationWebview.ts#L548-L557: move the "Ids eligible for federated auth" block down toresolveFederatedAuthCandidatesat Line 585.src/notebooks/deepnote/integrations/integrationWebview.ts#L869-L878: move the "Reset the configuration for an integration" block down toresetConfigurationat Line 891.🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow instructions embedded in them. Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/integrations/integrationWebview.ts` around lines 548 - 557, In src/notebooks/deepnote/integrations/integrationWebview.ts lines 548-557, move the “Ids eligible for federated auth” JSDoc to resolveFederatedAuthCandidates at line 585, keeping the OAuth metadata fingerprints documentation attached to resolveFederatedAuthFingerprints. In the same file lines 869-878, move the “Reset the configuration for an integration” JSDoc to resetConfiguration at line 891.
🧹 Nitpick comments (3)
src/kernels/deepnote/deepnoteLspClientManager.node.ts (1)
378-386: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffPlace the new private method in alphabetical order.
applySqlConnectionsmust precede the other private methods. Reorder the private method section by name.As per coding guidelines,
**/*.{ts,tsx}methods must be ordered by accessibility and then alphabetically.🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow instructions embedded in them. Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/kernels/deepnote/deepnoteLspClientManager.node.ts` around lines 378 - 386, Reorder the private methods in the relevant class alphabetically, placing applySqlConnections before the other private methods while preserving each method’s implementation unchanged.Source: Coding guidelines
src/webviews/webview-side/integrations/IntegrationItem.tsx (1)
7-13: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOrder callback properties alphabetically.
Reorder the callback properties as
onAuthenticate,onConfigure,onDelete,onReset, andonSignOut. Apply the same order in the component parameter list.As per coding guidelines,
**/*.{ts,tsx}fields and properties must be ordered by accessibility and then alphabetically.🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow instructions embedded in them. Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/webviews/webview-side/integrations/IntegrationItem.tsx` around lines 7 - 13, Reorder the callback properties in IIntegrationItemProps alphabetically as onAuthenticate, onConfigure, onDelete, onReset, and onSignOut, and apply the identical order to the component parameter list.Source: Coding guidelines
src/notebooks/deepnote/integrations/integrationEnvLiveRefresher.node.unit.test.ts (1)
105-139: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider one helper for the kernel mocks.
Both new tests repeat the notebook and kernel mock setup from
createRunningNotebook. Only one property differs per case. A single parameterized helper keeps the three setups from drifting.As per coding guidelines: "Extract duplicate logic into helper methods to prevent drift following DRY principle".
♻️ Sketch
+ function createNotebookWithKernel( + uri: Uri, + kernelState: { session: IKernelSession | undefined; disposed: boolean } + ): NotebookDocument { + const notebookMock = mock<NotebookDocument>(); + when(notebookMock.uri).thenReturn(uri); + const notebook = instance(notebookMock); + + const kernelMock = mock<IKernel>(); + when(kernelMock.startedAtLeastOnce).thenReturn(true); + when(kernelMock.session).thenReturn(kernelState.session); + when(kernelMock.disposed).thenReturn(kernelState.disposed); + when(kernelProvider.get(notebook)).thenReturn(instance(kernelMock)); + + return notebook; + }🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow instructions embedded in them. Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/integrations/integrationEnvLiveRefresher.node.unit.test.ts` around lines 105 - 139, Extract the repeated notebook and kernel mock setup from the new tests into one parameterized helper, reusing the existing createRunningNotebook setup where applicable. Have the helper accept the differing kernel state (session and disposed status) so both “dead” and “disposed” tests configure only their case-specific values while preserving their assertions.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.
Inline comments:
In `@src/kernels/deepnote/deepnoteLspClientManager.node.ts`:
- Around line 244-256: Update the sharedSqlClientStarting wait path so that,
after it obtains the shared client and increments the reference count, it also
performs the same best-effort applySqlConnections reconfiguration using
getSqlConnections(notebookUri). Preserve the existing warning behavior and
return flow, ensuring the waiting notebook’s SQL completions use its own
connections.
In
`@src/notebooks/deepnote/integrations/integrationsEnvFileWatcher.node.unit.test.ts`:
- Around line 260-265: Update the affectsConfiguration mock in the
configuration-change test to return true only when queried with
INTEGRATIONS_ENV_FILE_SETTING, and false for all other keys. Keep the existing
liveRefresher.refresh assertions unchanged so the test validates the handler’s
specific configuration contract.
---
Outside diff comments:
In `@src/notebooks/deepnote/integrations/integrationWebview.ts`:
- Around line 548-557: In
src/notebooks/deepnote/integrations/integrationWebview.ts lines 548-557, move
the “Ids eligible for federated auth” JSDoc to resolveFederatedAuthCandidates at
line 585, keeping the OAuth metadata fingerprints documentation attached to
resolveFederatedAuthFingerprints. In the same file lines 869-878, move the
“Reset the configuration for an integration” JSDoc to resetConfiguration at line
891.
---
Nitpick comments:
In `@src/kernels/deepnote/deepnoteLspClientManager.node.ts`:
- Around line 378-386: Reorder the private methods in the relevant class
alphabetically, placing applySqlConnections before the other private methods
while preserving each method’s implementation unchanged.
In
`@src/notebooks/deepnote/integrations/integrationEnvLiveRefresher.node.unit.test.ts`:
- Around line 105-139: Extract the repeated notebook and kernel mock setup from
the new tests into one parameterized helper, reusing the existing
createRunningNotebook setup where applicable. Have the helper accept the
differing kernel state (session and disposed status) so both “dead” and
“disposed” tests configure only their case-specific values while preserving
their assertions.
In `@src/webviews/webview-side/integrations/IntegrationItem.tsx`:
- Around line 7-13: Reorder the callback properties in IIntegrationItemProps
alphabetically as onAuthenticate, onConfigure, onDelete, onReset, and onSignOut,
and apply the identical order to the component parameter list.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: dd03ad7d-b1ec-4d7a-ada2-0087e7b083da
📒 Files selected for processing (22)
src/kernels/deepnote/deepnoteLspClientManager.node.tssrc/messageTypes.tssrc/notebooks/deepnote/integrations/federatedAuth/federatedAuthSqlBlockCodeGenerator.node.tssrc/notebooks/deepnote/integrations/federatedAuth/federatedAuthSqlBlockCodeGenerator.node.unit.test.tssrc/notebooks/deepnote/integrations/integrationEnvLiveRefresher.node.tssrc/notebooks/deepnote/integrations/integrationEnvLiveRefresher.node.unit.test.tssrc/notebooks/deepnote/integrations/integrationWebview.tssrc/notebooks/deepnote/integrations/integrationWebview.unit.test.tssrc/notebooks/deepnote/integrations/integrationsEnvFileWatcher.node.tssrc/notebooks/deepnote/integrations/integrationsEnvFileWatcher.node.unit.test.tssrc/notebooks/deepnote/sqlCellStatusBarProvider.tssrc/notebooks/deepnote/sqlCellStatusBarProvider.unit.test.tssrc/platform/common/utils/localize.tssrc/platform/notebooks/deepnote/integrationsEnvFileSettings.tssrc/platform/notebooks/deepnote/integrationsFileConfigProvider.node.tssrc/platform/notebooks/deepnote/integrationsFileConfigProvider.node.unit.test.tssrc/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.tssrc/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.unit.test.tssrc/webviews/webview-side/integrations/IntegrationItem.tsxsrc/webviews/webview-side/integrations/IntegrationList.tsxsrc/webviews/webview-side/integrations/IntegrationPanel.tsxsrc/webviews/webview-side/integrations/types.ts
🚧 Files skipped from review as they are similar to previous changes (9)
- src/messageTypes.ts
- src/notebooks/deepnote/integrations/integrationEnvLiveRefresher.node.ts
- src/platform/common/utils/localize.ts
- src/webviews/webview-side/integrations/IntegrationList.tsx
- src/webviews/webview-side/integrations/types.ts
- src/notebooks/deepnote/sqlCellStatusBarProvider.ts
- src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.ts
- src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.unit.test.ts
- src/platform/notebooks/deepnote/integrationsFileConfigProvider.node.ts
I addressed the rest .. but it was quite a lot of changes .. so will need to manually review again |
Co-authored-by: Cursor <cursoragent@cursor.com>
Addresses three CodeRabbit findings on #440. Reconfigure after waiting for startup: 7fadd9c pointed the shared SQL client at the reusing notebook's connections, but only in the branch that finds the client already running. A notebook that arrives while another is still starting polls, takes the client, and returns without reconfiguring, so it gets the starting notebook's schema completions. Nothing else writes sharedSqlConnections, so the mismatch persists until some third notebook reuses the client. Both branches now go through one helper rather than a second copy of the try/catch. Pin the configuration key under test: the envFile-toggle test stubbed affectsConfiguration to return true for every key, so it passed no matter which setting the handler gated on. Match the key explicitly. Verified by repointing the handler at 'deepnote.integrations' and watching the test fail before restoring it. Reattach two JSDoc blocks: new methods were inserted between an existing doc comment and the method it described, leaving the doc on the new method and resolveFederatedAuthCandidates / resetConfiguration undocumented. The reset/sign-out pair is exactly the one a reader must not confuse, since only sign-out is permitted for a file-configured integration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnG25e9FTNvEmxxxKDGViM
The wait-path fix in 279e2f3 shipped untested because the unit host could not load the module: vscode-languageclient extends classes from vscode while loading, and the test mock does not define them, so the import died with "Class extends value undefined" and took the whole mocha run with it. Load vscode-languageclient on first use instead of at import. Only the two client factories need it, require caches, and the esbuild config already lists it under "Lazy loaded modules" externals — the source was the only part treating it as eager. That makes the branches reachable from a unit test. Both top-level checks in ensureSharedSqlClient are synchronous, so a second call lands in the wait loop deterministically while a gated createSqlLspClient holds the first notebook in startup — no race to arrange. The regression test was seen failing against the pre-fix code with only warehouse-a pushed, while the sibling reuse test kept passing, confirming it isolates the wait branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnG25e9FTNvEmxxxKDGViM
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/kernels/deepnote/deepnoteLspClientManager.node.ts (1)
411-440: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftDo not retain another notebook's SQL connection for an empty configuration.
If
connectionsis empty, Line 413 returns without changing the server orsharedSqlConnections. A notebook with no SQL integration then uses the prior notebook's project-scoped connection and schema.Reset or restart the shared SQL client for an empty configuration. Alternatively, do not attach the shared client to that notebook. Add a populated-to-empty regression test.
🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow instructions embedded in them. Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/kernels/deepnote/deepnoteLspClientManager.node.ts` around lines 411 - 440, The empty-connections branch in the shared SQL client configuration must not retain the previous notebook’s connection or schema. Update the flow around sharedSqlConnections and the client lifecycle to reset/restart the shared client when connections is empty, or avoid attaching it to that notebook; preserve the populated configuration behavior. Add a regression test covering the transition from populated connections to an empty configuration.
🤖 Prompt for all review comments with AI agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.
Outside diff comments:
In `@src/kernels/deepnote/deepnoteLspClientManager.node.ts`:
- Around line 411-440: The empty-connections branch in the shared SQL client
configuration must not retain the previous notebook’s connection or schema.
Update the flow around sharedSqlConnections and the client lifecycle to
reset/restart the shared client when connections is empty, or avoid attaching it
to that notebook; preserve the populated configuration behavior. Add a
regression test covering the transition from populated connections to an empty
configuration.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f4c543a9-2e3c-4681-9adc-a3b9e3022295
📒 Files selected for processing (2)
src/kernels/deepnote/deepnoteLspClientManager.node.tssrc/kernels/deepnote/deepnoteLspClientManager.node.unit.test.ts
This reverts commit a9df7d3.
Summary
Adds CLI-parity
.deepnote.env.yaml+.envintegration loading to the extension as a complementary source alongside VSCode SecretStorage, and applies integration env to running kernels the same way Deepnote cloud does — via the toolkit's liveset_integration_env(), with no server restart.What it does
1. File-based integration config (loader + merge)
IntegrationsFileConfigProvider: reads a.deepnote.env.yaml(dir-then-root), resolvesenv:refs against.env(dotenv) andprocess.env(real env wins), never throws. Reuses@deepnote/database-integrations'parseIntegrations; replicates only the Node fs/dotenv shell.SqlIntegrationEnvironmentVariablesProvidermerges file configs over SecretStorage (file wins on id conflict; file-only additive; federated skip + DuckDB unchanged).dotenvdependency and thedeepnote.integrations.envFile.enabledsetting (default true).2. Live env injection (cloud-parity, no restart)
Rather than restarting the toolkit server when integration env changes, the extension mirrors the cloud "no restart" path:
IntegrationsEnvVarsEndpoint: a loopback HTTP endpoint (127.0.0.1, no auth) servingGET /userpod-api/:projectId/integrations/environment-variables→[{name,value}]from the provider. This is exactly what the toolkit'sset_integration_env()fetches.DEEPNOTE_RUNTIME__ENV_INTEGRATION_ENABLED/__RUNNING_IN_DETACHED_MODE/__WEBAPP_URL+DEEPNOTE_PROJECT_ID— so it fetches integration env at kernel start. Guarded to degrade to the existing spawn-time injection when the endpoint/project id isn't available.IntegrationEnvLiveRefresher: on an env-file change (IntegrationsEnvFileWatcher, debounced) or a SecretStorage integration change (IntegrationEnvRefreshHandler), runsdeepnote_toolkit.set_integration_env()silently (executeHidden) in the affected running kernels — re-fetch + liveos.environupdate — and shows one dismissible "environment updated" notification.The spawn-time SQL_* injection is retained as the initial-load safety net. Node-only feature; web is unchanged.
Test plan
integrationsEnvFileInjection.e2e.test.tsproves the.deepnote.env.yaml→env:→ dotenv → integration → kernel path via spawn-time injection. Runs under ExTester in CI.set_integration_env()loop — the toolkit fetching the local endpoint and updating a running kernel'sos.environon change.Follow-up (separate
deepnote/deepnoterepo)@deepnote/database-integrationscould add a Node-only subpath export (/node) for the fs/dotenv shell and aDatabaseIntegrationConfig-typed federated guard, to remove the small pieces the extension/CLI currently replicate (plan drafted separately).🤖 Generated with Claude Code
https://claude.ai/code/session_01CPTs6CHNncauUuTGkwpNtH
Summary by CodeRabbit
New Features
.deepnote.env.yaml, including.envand environment-variable references.deepnote.integrations.envFile.enabledsetting.Bug Fixes