feat(deepnote-file): Migration to single notebook deepnote file - #364
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (7)
📝 WalkthroughWalkthroughAdds Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 markdownlint-cli2 (0.22.1)skills/deepnote/SKILL.mdmarkdownlint-cli2 v0.22.1 (markdownlint v0.40.0) Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #364 +/- ##
==========================================
+ Coverage 84.51% 86.50% +1.98%
==========================================
Files 153 160 +7
Lines 8093 8380 +287
Branches 2189 2330 +141
==========================================
+ Hits 6840 7249 +409
+ Misses 1252 1130 -122
Partials 1 1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/cli/src/commands/lint.test.ts (1)
309-318:⚠️ Potential issue | 🟠 MajorAdd direct behavioral tests for
multi-notebook, not only allowlisting.Line 317 updates accepted codes, but this does not verify emission logic. Add assertions that the warning appears for multi-notebook projects and is suppressed when
--notebookis set.As per coding guidelines, "/*.test.{ts,tsx}: Create comprehensive tests for all new features using Vitest."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/commands/lint.test.ts` around lines 309 - 318, Add behavioral tests that assert the "multi-notebook" warning is actually emitted and suppressed: in packages/cli/src/commands/lint.test.ts (near the validCodes array and existing lint command tests) add two Vitest cases — one that runs the lint command against a synthetic multi-notebook project fixture and expects a warning with code "multi-notebook" to appear in the output/diagnostics, and a second that runs the same fixture but passes the CLI flag "--notebook" (or uses the helper that simulates that flag) and asserts that no "multi-notebook" warning is emitted; ensure you reference the existing validCodes array and the lint invocation helper (e.g., runLintCommand / invokeCli or whatever test helper is used) so the new tests integrate with the current suite.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli/src/commands/split.test.ts`:
- Around line 37-157: Add a test to split.test.ts that uses
createMultiNotebookFile/serializeDeepnoteFile to write a multi-notebook project
and also writes an accompanying snapshot file next to the source (e.g.,
`${inputPath}.snapshot`), then run the split via createSplitAction(program) and
assert that per-notebook snapshot files were created in the output directory
(one snapshot per generated .deepnote file) and that their contents deserialize
via deserializeDeepnoteFile and correspond to each notebook; use the existing
tempDir, fs helpers, and cleanup pattern consistent with the other tests and
mirror the checks used for generated .deepnote files.
In `@packages/cli/src/completions.ts`:
- Around line 155-162: The completion logic for the output option doesn't treat
`split` as a command, so when completing the `-o/--output` value Bash uses
generic file completion instead of directory completion; update the case that
handles `-o --output` (the branch that inspects `prev` for output flags) to
include `split)` alongside other command labels so the output-option handler
triggers directory-only completion for the `split` subcommand as well (look for
the `split)` case and the `COMPREPLY=( $(compgen -W "-o --output --force" --
"${cur}") )` logic to make the change).
In `@packages/cli/src/utils/analysis.ts`:
- Around line 212-218: The warning message uses nonInitNotebooks.length but
calls it "notebooks" which is misleading when file.project.initNotebookId
exists; update the issues.push message in the block that creates the
'multi-notebook' warning (the place using nonInitNotebooks and
file.project.initNotebookId) to either (a) report the actual total number of
notebooks using file.project.notebooks.length if you mean total notebooks, or
(b) explicitly say "non-init notebooks" and keep nonInitNotebooks.length if you
mean to count only non-init ones—make the message text consistent with the count
you choose.
In `@packages/cli/src/utils/output-persistence.test.ts`:
- Around line 133-137: Add a test case in
packages/cli/src/utils/output-persistence.test.ts that covers the multi-notebook
branch by constructing a fixture where file.project.notebooks.length > 1 and
asserting the produced path matches the legacy format
"{slug}_{projectId}_latest.snapshot.deepnote" (e.g., resolve(..., 'snapshots',
'test-project_test-project-id-1234-5678-90ab_latest.snapshot.deepnote'););
locate the existing single-notebook assertions around the resolve(...) lines and
duplicate the pattern for the multi-notebook scenario, ensuring the code path
exercised calls the same helper (the function under test used by those
assertions) so the legacy filename format remains covered during migration;
apply the same addition for the other mentioned blocks (around lines 149-153 and
165-169) to fully cover all branches.
In `@packages/convert/src/snapshot/split.ts`:
- Around line 149-152: The single-notebook branch returns the original file
object which breaks immutability consistency with the multi-notebook path that
returns new objects; update the single-notebook return to return a shallow copy
of file (e.g., spread properties) so callers that mutate results won't alter the
input — change the branch that uses nb = file.project.notebooks[0] and return [{
notebook: { id: nb.id, name: nb.name }, file }] to return an object where file
is a copied object instead of the original reference.
In `@packages/mcp/src/tools/snapshots.ts`:
- Around line 227-229: handleSnapshotSplit currently writes legacy filenames
like {slug}_{projectId}_... while reads (findSnapshotsForProject) are scoped by
notebookId; update handleSnapshotSplit (and the other snapshot-write sites
referenced around lines ~318-320 and ~490-491) to include the notebookId in the
snapshot filenames (or use a single filename-construction helper) so that split
single-notebook snapshots are saved as notebook-scoped names (e.g., include
notebookId in the "latest" and timestamped filename patterns), and ensure any
helper/function used by findSnapshotsForProject matches the new naming
convention.
---
Outside diff comments:
In `@packages/cli/src/commands/lint.test.ts`:
- Around line 309-318: Add behavioral tests that assert the "multi-notebook"
warning is actually emitted and suppressed: in
packages/cli/src/commands/lint.test.ts (near the validCodes array and existing
lint command tests) add two Vitest cases — one that runs the lint command
against a synthetic multi-notebook project fixture and expects a warning with
code "multi-notebook" to appear in the output/diagnostics, and a second that
runs the same fixture but passes the CLI flag "--notebook" (or uses the helper
that simulates that flag) and asserts that no "multi-notebook" warning is
emitted; ensure you reference the existing validCodes array and the lint
invocation helper (e.g., runLintCommand / invokeCli or whatever test helper is
used) so the new tests integrate with the current suite.
🪄 Autofix (Beta)
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: ASSERTIVE
Plan: Pro
Run ID: 911ea08d-0dc1-463a-bc0b-536ed8b957b0
📒 Files selected for processing (22)
FILES.mddocs/deepnote-format.mdpackages/cli/src/cli.tspackages/cli/src/commands/lint.test.tspackages/cli/src/commands/split.test.tspackages/cli/src/commands/split.tspackages/cli/src/completions.tspackages/cli/src/utils/analysis.tspackages/cli/src/utils/output-persistence.test.tspackages/cli/src/utils/output-persistence.tspackages/convert/src/cli.tspackages/convert/src/index.tspackages/convert/src/snapshot/index.tspackages/convert/src/snapshot/lookup.test.tspackages/convert/src/snapshot/lookup.tspackages/convert/src/snapshot/split.test.tspackages/convert/src/snapshot/split.tspackages/convert/src/snapshot/types.tspackages/convert/src/write-deepnote-file.tspackages/mcp/src/tools/execution.tspackages/mcp/src/tools/snapshots.tsskills/deepnote/SKILL.md
…d ensure unique output for notebooks with similar names
…e parameter object for improved clarity and consistency across the codebase
…okId-matching snapshots and improve sorting behavior
…tebook files are written
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
packages/cli/src/utils/output-persistence.test.ts (1)
125-168:⚠️ Potential issue | 🟡 MinorAdd a multi-notebook fallback test case.
These assertions only cover the single-notebook filename branch. Please add one case with
file.project.notebooks.length > 1and assert legacy format ({slug}_{projectId}_latest.snapshot.deepnote) is preserved.As per coding guidelines, "Create comprehensive tests for all new features using Vitest" and "Test edge cases, error handling, and special characters in test files".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/utils/output-persistence.test.ts` around lines 125 - 168, Add a test for the multi-notebook fallback branch of getSnapshotPath: use loadTestFile() to get a file, mutate file.project.notebooks to have length > 1 (e.g., push another notebook or set an array with two notebook entries), choose a sourcePath like '/path/to/project.deepnote', call getSnapshotPath(sourcePath, file) and assert the returned path uses the legacy multi-notebook filename format "{slug}_{projectId}_latest.snapshot.deepnote" (i.e., no notebook id in the filename) while still being resolved under resolve('/path/to', 'snapshots', ...); reference getSnapshotPath and file.project.notebooks to locate the code path to exercise.packages/cli/src/commands/split.test.ts (1)
203-227: 🧹 Nitpick | 🔵 TrivialMissing test for successful snapshot splitting.
Tests the warning path when snapshots fail, but no test verifies successful per-notebook snapshot generation. Consider adding a test that writes a valid snapshot alongside the source, then asserts per-notebook snapshots are created.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/commands/split.test.ts` around lines 203 - 227, Add a new test in packages/cli/src/commands/split.test.ts that mirrors the existing "should print a warning..." case but for the success path: use createMultiNotebookFile and serializeDeepnoteFile to write the input deepnote file, create a valid snapshot file (named like my-project_<projectId>_latest.snapshot.deepnote) containing a properly serialized per-notebook snapshot payload, call createSplitAction(program) and await action(inputPath, {}), then assert that per-notebook snapshot files (e.g., project-dashboard.snapshot.deepnote and project-data.snapshot.deepnote) exist in tempDir (use fs.access or similar) and that consoleSpy output does not contain the failure warning; reference createMultiNotebookFile, createSplitAction, serializeDeepnoteFile and consoleSpy when locating where to add the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli/src/commands/split.ts`:
- Around line 69-112: The final success line always reports "Split N snapshot(s)
into ..." using existingSnapshots.length even if some failed, which can mislead
users; update the code in the snapshot handling block (around
findSnapshotsForProject, loadSnapshotFile, splitSnapshotByNotebooks,
serializeDeepnoteSnapshot and the snapshotFailures array) to compute and report
the actual number of successfully split snapshots (e.g., successCount =
existingSnapshots.length - snapshotFailures.length) or include both counts
(e.g., "Split X of N snapshot(s); Y failed") and use that value in the output
call instead of existingSnapshots.length so the summary accurately reflects
failures.
In `@packages/cli/src/utils/output-persistence.ts`:
- Around line 107-113: The generated snapshot filenames pass notebookId verbatim
to generateSnapshotFilename, but parseSnapshotFilename only recognizes strict
UUID/hex notebook IDs; to fix, apply the same validation guard used by
parseSnapshotFilename to the notebookId before calling generateSnapshotFilename
(i.e., if notebookId does not match the UUID/hex regex, set it to undefined) —
update both occurrences where notebookId is computed (the notebookId assignment
used with generateSnapshotFilename around the current block and the similar
block at the later occurrence referenced in the comment) so filenames and lookup
parsing remain consistent.
---
Duplicate comments:
In `@packages/cli/src/commands/split.test.ts`:
- Around line 203-227: Add a new test in packages/cli/src/commands/split.test.ts
that mirrors the existing "should print a warning..." case but for the success
path: use createMultiNotebookFile and serializeDeepnoteFile to write the input
deepnote file, create a valid snapshot file (named like
my-project_<projectId>_latest.snapshot.deepnote) containing a properly
serialized per-notebook snapshot payload, call createSplitAction(program) and
await action(inputPath, {}), then assert that per-notebook snapshot files (e.g.,
project-dashboard.snapshot.deepnote and project-data.snapshot.deepnote) exist in
tempDir (use fs.access or similar) and that consoleSpy output does not contain
the failure warning; reference createMultiNotebookFile, createSplitAction,
serializeDeepnoteFile and consoleSpy when locating where to add the test.
In `@packages/cli/src/utils/output-persistence.test.ts`:
- Around line 125-168: Add a test for the multi-notebook fallback branch of
getSnapshotPath: use loadTestFile() to get a file, mutate file.project.notebooks
to have length > 1 (e.g., push another notebook or set an array with two
notebook entries), choose a sourcePath like '/path/to/project.deepnote', call
getSnapshotPath(sourcePath, file) and assert the returned path uses the legacy
multi-notebook filename format "{slug}_{projectId}_latest.snapshot.deepnote"
(i.e., no notebook id in the filename) while still being resolved under
resolve('/path/to', 'snapshots', ...); reference getSnapshotPath and
file.project.notebooks to locate the code path to exercise.
🪄 Autofix (Beta)
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: ASSERTIVE
Plan: Pro
Run ID: 3e2da83c-c06c-4332-b72d-c303f686b852
📒 Files selected for processing (15)
packages/cli/src/cli.tspackages/cli/src/commands/split.test.tspackages/cli/src/commands/split.tspackages/cli/src/utils/output-persistence.test.tspackages/cli/src/utils/output-persistence.tspackages/convert/src/index.tspackages/convert/src/snapshot/index.tspackages/convert/src/snapshot/lookup.test.tspackages/convert/src/snapshot/lookup.tspackages/convert/src/snapshot/snapshot-integration.test.tspackages/convert/src/snapshot/split.test.tspackages/convert/src/snapshot/split.tspackages/convert/src/snapshot/types.tspackages/convert/src/write-deepnote-file.tspackages/mcp/src/tools/execution.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@skills/deepnote/SKILL.md`:
- Around line 116-124: The "Location" example in SKILL.md must show the
single-notebook filename pattern so readers don't think {notebookId} is
optional; update the example block to include both patterns by adding an example
using `{slug}_{projectId}_{notebookId}_{timestamp}.snapshot.deepnote` alongside
the existing legacy `{slug}_{projectId}_{timestamp}.snapshot.deepnote`, and
ensure the explanatory text references the `{notebookId}` token explicitly so
users see when to use each format.
🪄 Autofix (Beta)
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: ASSERTIVE
Plan: Pro
Run ID: 5e4f06f0-8125-4da5-ae52-133558180336
📒 Files selected for processing (2)
docs/deepnote-format.mdskills/deepnote/SKILL.md
…s and enhance split functionality for non-init notebooks
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli/src/commands/split.test.ts`:
- Around line 236-261: Add a new test that mirrors the existing failure case but
uses a valid snapshot so we can assert successful per-notebook snapshot files
are created: create a test (e.g., "should split a valid snapshot into
per-notebook snapshots") that uses createMultiNotebookFile and writes the
project.deepnote via serializeDeepnoteFile to inputPath, create snapshotsDir and
write a valid snapshot file named using the same pattern
(`my-project_${projectId}_latest.snapshot.deepnote`) containing a properly
serialized snapshot (use the test helper you have for snapshots, analogous to
serializeDeepnoteFile), invoke the split action via createSplitAction(program)
and then assert that per-notebook snapshot files (e.g., names derived from the
notebook slugs like project-dashboard_<projectId>_latest.snapshot.deepnote and
project-data_...snapshot.deepnote) exist in snapshotsDir and contain the
expected snapshot content; reference createSplitAction, createMultiNotebookFile,
serializeDeepnoteFile, tempDir, snapshotsDir and projectId to locate where to
change/add the test.
In `@packages/cli/src/commands/split.ts`:
- Around line 93-101: The code directly reads initSnapshot.project.notebooks[0]
and mainSnapshot.project.notebooks[0] which can be undefined if either notebooks
array is empty; update the logic in the split handler to defensively check that
initSnapshot.project.notebooks and mainSnapshot.project.notebooks are non-empty
before assigning initNb and mainNb (e.g., validate array length or presence),
and if either is empty, handle it explicitly (throw a clear error or fallback)
before constructing nbSnapshot so nbSnapshot.project.notebooks never contains
undefined entries.
🪄 Autofix (Beta)
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: ASSERTIVE
Plan: Pro
Run ID: 3b99d1e1-e32c-486f-be01-e937699a9483
📒 Files selected for processing (6)
packages/cli/src/commands/split.test.tspackages/cli/src/commands/split.tspackages/convert/src/index.tspackages/convert/src/snapshot/index.tspackages/convert/src/snapshot/split.test.tspackages/convert/src/snapshot/split.ts
- Add resolveSnapshotNotebookId for single-notebook and init+main split files so snapshot paths stay distinct per main notebook when project.id is shared. - Route snapshot save/load/merge and MCP split through the helper and generateSnapshotFilename. - Extend parseSnapshotFilename notebook segment to match generateSnapshotFilename output (e.g. notebook-1, nb-1) while keeping legacy two-segment names. - Add regression tests for init+main saves, sibling isolation, MCP split/load, and non-UUID filename parsing. Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/cli/src/utils/output-persistence.test.ts (1)
270-333:⚠️ Potential issue | 🟡 MinorAdd a true multi-notebook legacy-path case.
This new coverage pins down the init+main branch, but the backward-compatible branch where
resolveSnapshotNotebookId(...)returnsundefinedis still untested. Please add one fixture with either 3+ notebooks or 2 notebooks withoutinitNotebookIdand assert it keeps the legacy{slug}_{projectId}_latest.snapshot.deepnoteshape.As per coding guidelines, "Create comprehensive tests for all new features using Vitest" and "Test edge cases, error handling, and special characters in test files".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/utils/output-persistence.test.ts` around lines 270 - 333, The test suite is missing a case for the legacy multi-notebook path when resolveSnapshotNotebookId(...) returns undefined; add a new test (in packages/cli/src/utils/output-persistence.test.ts) that constructs a DeepnoteFile whose project either has 3+ notebooks or has 2 notebooks but no initNotebookId, call saveExecutionSnapshot(sourcePath, file, outputs, timing) and assert the returned result.snapshotPath and result.timestampedSnapshotPath follow the legacy pattern (contain `_{projectId}_latest.snapshot.deepnote` or the `{slug}_{projectId}_latest.snapshot.deepnote` shape). Use the same sourcePath/outputs/timing variables pattern as the existing test so the new test verifies the fallback branch in saveExecutionSnapshot and the behavior of resolveSnapshotNotebookId.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/mcp/src/tools/execution.ts`:
- Around line 268-275: Existing code duplicates notebook-aware snapshot filename
logic; extract a shared helper that centralizes slug/notebook/timestamp filename
generation and path resolution so other modules can reuse it. Create a function
(e.g., buildSnapshotPaths or makeSnapshotFilenames) that takes the inputs used
here (file, slug, snapshotDir, timing/finishedAt) and internally calls
resolveSnapshotNotebookId and generateSnapshotFilename to produce both the
timestamped and latest filenames and their resolved paths
(timestampedSnapshotPath, snapshotPath), keeping the timestamp formatting logic
identical (new Date(...).toISOString().replace(/[:.]/g, '-').slice(0, 19)); then
replace the duplicated block in this file to call the new helper and update
other places (where the same logic exists) to use it too so future
snapshot-format changes are made in one spot.
---
Duplicate comments:
In `@packages/cli/src/utils/output-persistence.test.ts`:
- Around line 270-333: The test suite is missing a case for the legacy
multi-notebook path when resolveSnapshotNotebookId(...) returns undefined; add a
new test (in packages/cli/src/utils/output-persistence.test.ts) that constructs
a DeepnoteFile whose project either has 3+ notebooks or has 2 notebooks but no
initNotebookId, call saveExecutionSnapshot(sourcePath, file, outputs, timing)
and assert the returned result.snapshotPath and result.timestampedSnapshotPath
follow the legacy pattern (contain `_{projectId}_latest.snapshot.deepnote` or
the `{slug}_{projectId}_latest.snapshot.deepnote` shape). Use the same
sourcePath/outputs/timing variables pattern as the existing test so the new test
verifies the fallback branch in saveExecutionSnapshot and the behavior of
resolveSnapshotNotebookId.
🪄 Autofix (Beta)
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: ASSERTIVE
Plan: Pro
Run ID: 8acef060-70c7-4e68-b4c5-5b136fb5a355
📒 Files selected for processing (13)
packages/cli/src/utils/output-persistence.test.tspackages/cli/src/utils/output-persistence.tspackages/convert/src/cli.tspackages/convert/src/index.tspackages/convert/src/snapshot/index.tspackages/convert/src/snapshot/lookup.test.tspackages/convert/src/snapshot/lookup.tspackages/convert/src/snapshot/snapshot-integration.test.tspackages/convert/src/snapshot/snapshot-notebook-id.tspackages/convert/src/write-deepnote-file.tspackages/mcp/src/tools/execution.tspackages/mcp/src/tools/snapshots.test.tspackages/mcp/src/tools/snapshots.ts
- Add logic to resolve and compose init notebooks alongside main notebooks during execution. - Enhance project setup to include init block IDs and notebook IDs for proper execution flow. - Update validation to ensure required integrations are detected for both init and main notebooks. - Introduce tests to verify the correct loading and execution of sibling init notebooks, ensuring they are prioritized correctly in the execution order. - Refactor related functions to support the new init resolution logic, improving overall clarity and maintainability.
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 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.
Inline comments:
In `@packages/cli/src/commands/run.ts`:
- Around line 205-213: The preflight plumbing is using initNotebookName to key
the init notebook scope which can collide when names aren't unique; update all
places that reference or store initNotebookName to use initNotebookId instead so
the init scope is keyed by the unique identifier. Search for uses of
initNotebookName (including where scope/inputs/dry-run/requirement checks are
built) and replace them with initNotebookId, ensuring variables, function
parameters and any maps/lookup keys (e.g., where the init notebook is added to
scope, listed in --list-inputs, included in dry-run plans, and in requirement
checks) are switched to use initNotebookId and that initNotebookName is only
retained for diagnostics where uniqueness is not required. Verify
functions/methods that accept the notebook identifier (references to
initNotebookId) are passed the id and update any validation or filter logic that
previously compared names to compare ids instead.
In `@packages/cli/src/commands/split.ts`:
- Around line 125-126: The snapshot writes (creating snapshotPath via join and
calling fs.writeFile with serializeDeepnoteSnapshot(nbSnapshot)) always use the
default write mode and ignore the CLI --force option; update the fs.writeFile
calls in split.ts (the ones that write snapshotPath and the other write at lines
~141-142) to respect the force flag by passing write options: use
exclusive-write mode when force is false (e.g., flag 'wx') and normal overwrite
when force is true (flag 'w'), so the writes will error if a snapshot exists
unless --force was provided.
In `@packages/convert/src/load-runnable-file.ts`:
- Around line 78-85: The .deepnote branch in loadRunnableFile currently catches
fs.readFile errors and throws a new LoadRunnableFileError, which breaks the
contract that filesystem errors are propagated; modify the catch in the ext ===
'.deepnote' block (around rawBytes/absolutePath) to rethrow the original
readError (or remove the try/catch) instead of wrapping it in
LoadRunnableFileError so callers receive the original errno/code details.
In `@packages/convert/src/snapshot/resolve-init.ts`:
- Around line 345-350: The normalization in areSettingsEqual currently treats
undefined/null and {} differently; update the norm function so that undefined,
null, and an empty plain object ({}) all normalize to the same sentinel (e.g.,
'null') before JSON.stringify with sortedKeysReplacer. Concretely, inside
areSettingsEqual's norm, check if value is undefined or null or (typeof value
=== 'object' && value !== null && Object.keys(value).length === 0) and return
'null' in those cases, otherwise return JSON.stringify(value,
sortedKeysReplacer).
In `@packages/mcp/src/tools/execution.ts`:
- Around line 322-333: The code currently maps a notebookFilter match back to
engineNotebookName (string) which loses the exact-ID selection; instead, when
notebookFilter matched by id (i.e., found.id === notebookFilter or original
filter looked like an id), do not collapse to name—leave notebookName undefined
and include the exact id in preludeNotebookIds (use targetNotebookId / found.id)
so engine.runProject receives the precise notebook id via preludeNotebookIds;
alternatively, if you must use notebookName, detect duplicate notebook names in
file.project.notebooks and throw/return an error when duplicates exist to avoid
ambiguous selection. Ensure changes touch the variables engineNotebookName,
targetNotebookId, and the call to engine.runProject (preludeNotebookIds /
notebookName) so selection is unambiguous.
In `@skills/deepnote/SKILL.md`:
- Around line 274-287: The Markdown table has a long unwrapped cell (the
"deepnote split <path>" row) causing Prettier/formatting failures; fix by
reflowing that long description into shorter lines or run Prettier to auto-wrap
the table so the row fits within line-length limits, ensuring the table syntax
remains valid and all pipes/columns (e.g., the "deepnote split <path>" row)
align with the other rows.
🪄 Autofix (Beta)
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: ASSERTIVE
Plan: Pro
Run ID: b11ed399-6f20-48de-bd6a-77f2f4ed506f
📒 Files selected for processing (21)
packages/cli/src/commands/run.test.tspackages/cli/src/commands/run.tspackages/cli/src/commands/split.test.tspackages/cli/src/commands/split.tspackages/cli/src/integrations/collect-integrations.tspackages/cli/src/utils/format-converter.tspackages/cli/src/utils/output-persistence.tspackages/convert/src/index.tspackages/convert/src/load-runnable-file.tspackages/convert/src/snapshot/index.tspackages/convert/src/snapshot/resolve-init.test.tspackages/convert/src/snapshot/resolve-init.tspackages/convert/src/snapshot/save-execution-snapshot.tspackages/convert/src/snapshot/snapshot-notebook-id.tspackages/convert/src/snapshot/split.test.tspackages/convert/src/snapshot/split.tspackages/convert/src/snapshot/types.tspackages/mcp/src/tools/execution.test.tspackages/mcp/src/tools/execution.tspackages/runtime-core/src/execution-engine.tsskills/deepnote/SKILL.md
Notebook id, project id, and slug are parsed from untrusted .deepnote files as arbitrary strings and interpolated into snapshot filenames that callers join to the snapshots/ directory and write to disk. A value containing path separators or `..` could escape that directory and overwrite arbitrary files. Run each attacker-derived component through sanitizeFilenameComponent ([^A-Za-z0-9_-] -> _) at the single chokepoint feeding all write sinks; this is a no-op for the ids snapshot readers expect (UUIDs, 32-char hex, notebook-1). timestamp is internally generated and left intact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a multi-notebook .deepnote file sets initNotebookId to an id that matches no notebook (e.g. a deleted/renamed notebook), splitByNotebooks emitted no init entry but still spread the dangling initNotebookId into every per-notebook output. `deepnote run` then attempted sibling-init resolution, found no matching init file, and failed with exit 2 — so a "successful" split produced non-runnable files. Detect the dangling case (initNotebookId set but no matching notebook) and delete initNotebookId from each emitted entry so the splits are plain no-init files. The valid-init path still preserves the id for the sibling-init resolver. Tighten the missing-notebook test to assert the id is dropped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On a split/init file with init prelude blocks, a misspelled or non-executable --block was folded into the engine filter alongside the executable init blocks. That kept the filter non-empty, so the engine's missing/non-executable-target validation was skipped: only init ran and the command exited 0 without ever running the requested block (the dry-run plan had the same blind spot). Validate options.block via assertExecutableBlockExists in the shared buildEngineExecutionScope before init prelude ids are folded in, guarded on initBlockIds.size > 0 (the bug condition). Plain files are still validated downstream (engine for run, collectExecutableBlocks for dry-run), so their behavior is unchanged. Split/init files now fail loudly with the same error as plain files. Covers both run and dry-run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/cli/src/commands/run.ts (1)
535-550:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftKeep this preflight scope keyed by init notebook ID.
This new guard still resolves its prelude scope through
additionalNotebookNames, so a same-named non-init notebook can satisfyassertExecutableBlockExists()even though execution stays keyed bypreludeNotebookIds. That keeps--blockpreflight/dry-run out of sync with the engine on split projects with duplicate notebook names.Thread
initNotebookIdthrough these scope helpers and keepinitNotebookNamediagnostic-only.🤖 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 `@packages/cli/src/commands/run.ts` around lines 535 - 550, The preflight block validation uses getNotebooksForExecutionScope with additionalNotebookNames and can pick a same-named non-init notebook; instead thread the init notebook identity (initNotebookId) into the scope resolution so the preflight scope matches the engine's execution scope instead of relying on initNotebookName. Update the call-site around options.block && initBlockIds to pass initNotebookId to getNotebooksForExecutionScope (and any downstream helpers it calls) and keep initNotebookName only for logging/diagnostics; ensure assertExecutableBlockExists is invoked with the notebook list resolved using initNotebookId so the dry-run/preflight and engine use the same notebook-keyed scope.
🤖 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.
Inline comments:
In `@packages/convert/src/snapshot/split.ts`:
- Around line 54-61: The filename builder generateSnapshotFilename currently
sanitizes slug, projectId and notebookId but interpolates timestamp raw; update
generateSnapshotFilename to pass timestamp through sanitizeFilenameComponent
(e.g. const safeTimestamp = sanitizeFilenameComponent(timestamp)) and use
safeTimestamp in both the notebookId and non-notebookId return branches so the
produced basename cannot contain traversal or unsafe characters while preserving
existing sanitization of slug/projectId/notebookId.
---
Duplicate comments:
In `@packages/cli/src/commands/run.ts`:
- Around line 535-550: The preflight block validation uses
getNotebooksForExecutionScope with additionalNotebookNames and can pick a
same-named non-init notebook; instead thread the init notebook identity
(initNotebookId) into the scope resolution so the preflight scope matches the
engine's execution scope instead of relying on initNotebookName. Update the
call-site around options.block && initBlockIds to pass initNotebookId to
getNotebooksForExecutionScope (and any downstream helpers it calls) and keep
initNotebookName only for logging/diagnostics; ensure
assertExecutableBlockExists is invoked with the notebook list resolved using
initNotebookId so the dry-run/preflight and engine use the same notebook-keyed
scope.
🪄 Autofix (Beta)
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: ASSERTIVE
Plan: Pro
Run ID: 09542121-acf1-41e3-a64d-c8adf6601248
📒 Files selected for processing (4)
packages/cli/src/commands/run.test.tspackages/cli/src/commands/run.tspackages/convert/src/snapshot/split.test.tspackages/convert/src/snapshot/split.ts
Add Bash directory completion for `deepnote split -o/--output`, document single-notebook snapshot naming in the skill file, and reformat the CLI reference table so Prettier passes. Co-authored-by: Cursor <cursoragent@cursor.com>
Trim comments introduced in this branch to at most a single line: - multi-line // blocks and JSDoc condensed to a one-line summary - comments on newly-added properties dropped where sibling fields are uncommented - self-evident narration removed, incl. Arrange/Act/Assert test markers - pre-existing comments and functional directives (biome-ignore, etc.) left untouched Comment-only change; no code modified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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 (3)
packages/cli/src/commands/run.test.ts (1)
1910-2383: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winAdd one sibling-init regression for duplicate notebook names.
Add a case where a non-init notebook shares
initNotebookName, then assert preflight scope still tracks the true init notebook only. This will lock in the ID-based fix and prevent regressions.🤖 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 `@packages/cli/src/commands/run.test.ts` around lines 1910 - 2383, Add a new test case to cover the regression where a non-init notebook shares the same name as the init notebook: create files via makeInitFile()/makeMainFile() plus an extra sibling notebook file whose notebook.name equals the init notebook's name, call action(mainPath, { notebook: 'Main' }) (or the appropriate invocation used in similar tests), then assert mockRunProject was called once and that the runOptions.preludeNotebookIds is a Set containing only INIT_NB_ID (and that the project's notebooks order remains [INIT_NB_ID, MAIN_NB_ID]); place this alongside the other "sibling init resolution" tests so it exercises the resolver and preflight logic (referencing action, mockRunProject, and runOptions.preludeNotebookIds).packages/cli/src/commands/split.test.ts (1)
323-467: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winAdd explicit snapshot-collision tests for
--forcebehavior.Current tests cover snapshot splitting and corrupt-snapshot warnings, but not “existing snapshot output already exists” behavior. Add one case for default mode (must not overwrite) and one for
force: true(must overwrite).🤖 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 `@packages/cli/src/commands/split.test.ts` around lines 323 - 467, Add two tests around createSplitAction: one asserting that when a snapshot file already exists the split action does not overwrite it by default (createSplitAction(program); call action(inputPath, {}) and then verify the original snapshot file content remains unchanged and no new snapshot replace occurs), and a second asserting that when invoked with force: true (call action(inputPath, { force: true }) or the equivalent option) the existing snapshot is overwritten (write an identifiable original snapshot to snapshotsDir before running, run action, then assert the snapshot file content has changed to the new split output). Reference createSplitAction, action(inputPath, {}), action(inputPath, { force: true }), snapshotsDir, and the snapshot filenames to locate where to add these assertions.packages/mcp/src/tools/execution.ts (1)
524-533:⚠️ Potential issue | 🟠 Major | ⚡ Quick winInit block runs currently widen to the whole init notebook.
effectiveBlockIdsis built frominitBlockIdswhenever composition is active. If the requestedblockIdis itself inside the init notebook, this schedules every executable init block instead of just the requested block. Gate the prelude ontargetNotebook.id !== initNotebookId; the dry-run branch should mirror the same check.Minimal fix
- const effectiveBlockIds = initBlockIds.size > 0 ? [...initBlockIds, targetBlock.id] : undefined + const includeInitPrelude = + initNotebookId !== undefined && targetNotebook.id !== initNotebookId && initBlockIds.size > 0 + const effectiveBlockIds = includeInitPrelude ? [...initBlockIds, targetBlock.id] : undefined const summary = await engine.runProject(file, { notebookName: targetNotebook.name, blockId: effectiveBlockIds === undefined ? targetBlock.id : undefined, blockIds: effectiveBlockIds, - preludeNotebookIds: - initNotebookId !== undefined && initNotebookId !== targetNotebook.id ? new Set([initNotebookId]) : undefined, + preludeNotebookIds: includeInitPrelude ? new Set([initNotebookId]) : undefined, inputs,🤖 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 `@packages/mcp/src/tools/execution.ts` around lines 524 - 533, The current composition always expands initBlockIds into effectiveBlockIds even when the requested targetBlock sits in the same init notebook, which causes all executable init blocks to run; update the logic around effectiveBlockIds (and the engine.runProject preludeNotebookIds and the dry-run branch) to only compose and include initBlockIds when initNotebookId !== targetNotebook.id (i.e., gate the prelude on targetNotebook.id !== initNotebookId) so that if the target block is inside the init notebook we pass only that blockId, not the whole init notebook's executable blocks; apply the same conditional in the dry-run branch to mirror the behavior.
♻️ Duplicate comments (6)
packages/cli/src/commands/split.ts (2)
149-149:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winSnapshot success summary is inaccurate on partial failure.
Line 149 always reports total discovered snapshots, even when some snapshot splits failed and were logged as warnings.
🤖 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 `@packages/cli/src/commands/split.ts` at line 149, The summary message uses existingSnapshots.length but does not account for failures; change the split logic to count only successful splits (e.g., introduce a successfulSplits counter updated inside the block that performs each split) and then replace existingSnapshots.length with that counter in the output call (the line calling output(`\n ${c.dim(`Split ${existingSnapshots.length} snapshot(s) into ${snapshotDir}`)}`)). Ensure the counter is initialized before processing, incremented only on confirmed success (not on warnings/errors), and used in the final output so the message reflects partial failures accurately.
117-117:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSnapshot writes still bypass
--forceand can overwrite existing files.Line 117 and Line 132 use default write mode (
'w'), so reruns clobber existing*.snapshot.deepnoteoutputs even when--forceis not set.Suggested patch
- await fs.writeFile(snapshotPath, serializeDeepnoteSnapshot(nbSnapshot), 'utf-8') + try { + await fs.writeFile(snapshotPath, serializeDeepnoteSnapshot(nbSnapshot), { + encoding: 'utf-8', + flag: force ? 'w' : 'wx', + }) + } catch (err) { + if (!force && isErrnoException(err, 'EEXIST')) { + throw new Error(`Output file already exists: ${snapshotPath}. Use --force to overwrite.`) + } + throw err + } @@ - await fs.writeFile(snapshotPath, serializeDeepnoteSnapshot(initSnapshot), 'utf-8') + try { + await fs.writeFile(snapshotPath, serializeDeepnoteSnapshot(initSnapshot), { + encoding: 'utf-8', + flag: force ? 'w' : 'wx', + }) + } catch (err) { + if (!force && isErrnoException(err, 'EEXIST')) { + throw new Error(`Output file already exists: ${snapshotPath}. Use --force to overwrite.`) + } + throw err + }Also applies to: 132-132
🤖 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 `@packages/cli/src/commands/split.ts` at line 117, The snapshot writes currently call fs.writeFile(snapshotPath, serializeDeepnoteSnapshot(...), 'utf-8') and always clobber files; update both occurrences (the writes using snapshotPath and serializeDeepnoteSnapshot at lines noted) to honor the CLI --force flag (likely exposed as a boolean like force or flags.force). Use write options: if force is true use 'w' (or default) else use { encoding: 'utf-8', flag: 'wx' } so the write will fail if the file exists, and catch EEXIST to surface a friendly error/exit; ensure both write sites are changed consistently and any error handling uses the same message.packages/cli/src/commands/run.ts (1)
346-349:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftPrelude scope is keyed by notebook name; this is ambiguous and can target the wrong notebook.
These paths include init/prelude notebooks via names, not IDs. If two notebooks share a name, preflight scope can diverge from runtime scope (which uses
preludeNotebookIdsby ID).Use
additionalNotebookIdsend-to-end (scope selection, input listing, validation, and integration collection), and only keep names for display.Also applies to: 437-444, 747-748, 928-929
🤖 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 `@packages/cli/src/commands/run.ts` around lines 346 - 349, The code calls collectRequiredIntegrationIds(file, options.notebook, { additionalNotebookNames: initNotebookName ? [initNotebookName] : [] }) which passes the init/prelude notebook by name (initNotebookName) causing ambiguous scope when multiple notebooks share a name; replace name-based usage with ID-based propagation: compute the init/prelude notebook ID (use preludeNotebookIds or resolve initNotebookName to its ID earlier) and pass it via additionalNotebookIds to collectRequiredIntegrationIds and all related flows (scope selection, input listing, validation, integration collection), keep notebook names only for display; update the other occurrences that pass additionalNotebookNames (the blocks referenced around the other ranges) to use additionalNotebookIds consistently.packages/convert/src/load-runnable-file.ts (1)
43-43:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAlign
.deepnoteread-error behavior with the stated contract.Line 43 says fs errors are propagated unwrapped, but Lines 63-68 wrap
.deepnoteread failures inLoadRunnableFileError. This makes.deepnotebehave differently from.ipynb/.py/.qmdreads and changes upstream error handling semantics.Suggested fix
if (ext === '.deepnote') { - let rawBytes: Buffer - try { - rawBytes = await fs.readFile(absolutePath) - } catch (readError) { - const message = readError instanceof Error ? readError.message : String(readError) - throw new LoadRunnableFileError(`Failed to read .deepnote file: ${absolutePath}\n\nRead error: ${message}`) - } + const rawBytes = await fs.readFile(absolutePath)Also applies to: 61-68
🤖 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 `@packages/convert/src/load-runnable-file.ts` at line 43, The .deepnote read currently wraps read failures in LoadRunnableFileError, breaking the stated contract that filesystem errors should be propagated unwrapped; in the loadRunnableFile implementation, update the .deepnote file-read path (the block that currently catches errors at lines handling ".deepnote") to stop catching and rewrapping raw fs errors—only convert non-fs/content-validation errors to LoadRunnableFileError (or rethrow after inspecting error type), and let native fs errors bubble up unchanged so .deepnote behaves consistently with .ipynb/.py/.qmd; reference LoadRunnableFileError and the .deepnote read block when making this change.packages/convert/src/snapshot/split.ts (1)
33-50:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSanitize
timestamptoo.
slug,projectId, andnotebookIdare sanitized, buttimestampstill flows into the basename verbatim. As written,generateSnapshotFilename({ ..., timestamp: '../x' })can escape the target snapshot directory once callersjoin()and write the result.Suggested fix
export function generateSnapshotFilename(params: GenerateSnapshotFilenameParams): string { const { slug, projectId, notebookId, timestamp = 'latest' } = params const safeSlug = sanitizeFilenameComponent(slug) const safeProjectId = sanitizeFilenameComponent(projectId) + const safeTimestamp = sanitizeFilenameComponent(timestamp) if (notebookId) { - return `${safeSlug}_${safeProjectId}_${sanitizeFilenameComponent(notebookId)}_${timestamp}.snapshot.deepnote` + const safeNotebookId = sanitizeFilenameComponent(notebookId) + return `${safeSlug}_${safeProjectId}_${safeNotebookId}_${safeTimestamp}.snapshot.deepnote` } - return `${safeSlug}_${safeProjectId}_${timestamp}.snapshot.deepnote` + return `${safeSlug}_${safeProjectId}_${safeTimestamp}.snapshot.deepnote` }🤖 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 `@packages/convert/src/snapshot/split.ts` around lines 33 - 50, The timestamp parameter is not sanitized causing path-traversal when interpolated into the filename; update generateSnapshotFilename to pass timestamp through sanitizeFilenameComponent (use sanitizeFilenameComponent(timestamp || 'latest')) before building the basename so the returned string always uses safeSlug, safeProjectId, sanitized notebookId and sanitized timestamp (preserving the .snapshot.deepnote suffix) to prevent directory escape; reference generateSnapshotFilename and sanitizeFilenameComponent when making the change.packages/mcp/src/tools/execution.ts (1)
237-250:⚠️ Potential issue | 🟠 Major | ⚡ Quick winNotebook selection is still ambiguous when names collide.
Dry-run picks a single notebook via
find(...), but actual execution collapses that choice back tofound.namebefore callingengine.runProject. The engine filters by name, so passing an ID for one duplicate-name notebook can still execute every notebook sharing that name, while the preview shows only one. Fail fast on non-unique names here until the engine can target notebook IDs directly.Suggested guard
let notebooks = file.project.notebooks + let selectedNotebook: DeepnoteFile['project']['notebooks'][number] | undefined if (notebookFilter) { - const found = file.project.notebooks.find(n => n.name === notebookFilter || n.id === notebookFilter) - if (!found) { + const matches = file.project.notebooks.filter(n => n.name === notebookFilter || n.id === notebookFilter) + selectedNotebook = matches.find(n => n.id === notebookFilter) ?? matches[0] + if (!selectedNotebook) { return { content: [{ type: 'text', text: `Notebook not found: ${notebookFilter}` }], isError: true, } } + const nameCollisions = file.project.notebooks.filter(n => n.name === selectedNotebook.name) + if (nameCollisions.length > 1) { + return { + content: [ + { + type: 'text', + text: `Notebook filter "${notebookFilter}" is ambiguous because notebook name "${selectedNotebook.name}" is duplicated. Use a unique notebook name.`, + }, + ], + isError: true, + } + } const initNotebook = initNotebookId !== undefined ? file.project.notebooks.find(n => n.id === initNotebookId) : undefined - notebooks = initNotebook !== undefined && initNotebook.id !== found.id ? [initNotebook, found] : [found] + notebooks = + initNotebook !== undefined && initNotebook.id !== selectedNotebook.id + ? [initNotebook, selectedNotebook] + : [selectedNotebook] } @@ - if (notebookFilter) { - const found = file.project.notebooks.find(n => n.name === notebookFilter || n.id === notebookFilter) - engineNotebookName = found?.name - targetNotebookId = found?.id - } + if (selectedNotebook) { + engineNotebookName = selectedNotebook.name + targetNotebookId = selectedNotebook.id + }Also applies to: 310-322
🤖 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 `@packages/mcp/src/tools/execution.ts` around lines 237 - 250, The selection logic for notebookFilter uses find(...) and then later relies on engine.runProject filtering by name, which causes ambiguity when multiple notebooks share the same name; update the block that computes notebooks (variables: notebookFilter, found, initNotebook, file.project.notebooks) to detect when notebookFilter matched by name but more than one notebook has that name and fail fast with an error asking for the notebook ID (or require using an ID match), instead of proceeding; implement the same uniqueness guard in the analogous selection code later (the other notebooks filtering block) so we never pass an ambiguous name to engine.runProject.
🤖 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 `@packages/cli/src/commands/run.test.ts`:
- Around line 1910-2383: Add a new test case to cover the regression where a
non-init notebook shares the same name as the init notebook: create files via
makeInitFile()/makeMainFile() plus an extra sibling notebook file whose
notebook.name equals the init notebook's name, call action(mainPath, { notebook:
'Main' }) (or the appropriate invocation used in similar tests), then assert
mockRunProject was called once and that the runOptions.preludeNotebookIds is a
Set containing only INIT_NB_ID (and that the project's notebooks order remains
[INIT_NB_ID, MAIN_NB_ID]); place this alongside the other "sibling init
resolution" tests so it exercises the resolver and preflight logic (referencing
action, mockRunProject, and runOptions.preludeNotebookIds).
In `@packages/cli/src/commands/split.test.ts`:
- Around line 323-467: Add two tests around createSplitAction: one asserting
that when a snapshot file already exists the split action does not overwrite it
by default (createSplitAction(program); call action(inputPath, {}) and then
verify the original snapshot file content remains unchanged and no new snapshot
replace occurs), and a second asserting that when invoked with force: true (call
action(inputPath, { force: true }) or the equivalent option) the existing
snapshot is overwritten (write an identifiable original snapshot to snapshotsDir
before running, run action, then assert the snapshot file content has changed to
the new split output). Reference createSplitAction, action(inputPath, {}),
action(inputPath, { force: true }), snapshotsDir, and the snapshot filenames to
locate where to add these assertions.
In `@packages/mcp/src/tools/execution.ts`:
- Around line 524-533: The current composition always expands initBlockIds into
effectiveBlockIds even when the requested targetBlock sits in the same init
notebook, which causes all executable init blocks to run; update the logic
around effectiveBlockIds (and the engine.runProject preludeNotebookIds and the
dry-run branch) to only compose and include initBlockIds when initNotebookId !==
targetNotebook.id (i.e., gate the prelude on targetNotebook.id !==
initNotebookId) so that if the target block is inside the init notebook we pass
only that blockId, not the whole init notebook's executable blocks; apply the
same conditional in the dry-run branch to mirror the behavior.
---
Duplicate comments:
In `@packages/cli/src/commands/run.ts`:
- Around line 346-349: The code calls collectRequiredIntegrationIds(file,
options.notebook, { additionalNotebookNames: initNotebookName ?
[initNotebookName] : [] }) which passes the init/prelude notebook by name
(initNotebookName) causing ambiguous scope when multiple notebooks share a name;
replace name-based usage with ID-based propagation: compute the init/prelude
notebook ID (use preludeNotebookIds or resolve initNotebookName to its ID
earlier) and pass it via additionalNotebookIds to collectRequiredIntegrationIds
and all related flows (scope selection, input listing, validation, integration
collection), keep notebook names only for display; update the other occurrences
that pass additionalNotebookNames (the blocks referenced around the other
ranges) to use additionalNotebookIds consistently.
In `@packages/cli/src/commands/split.ts`:
- Line 149: The summary message uses existingSnapshots.length but does not
account for failures; change the split logic to count only successful splits
(e.g., introduce a successfulSplits counter updated inside the block that
performs each split) and then replace existingSnapshots.length with that counter
in the output call (the line calling output(`\n ${c.dim(`Split
${existingSnapshots.length} snapshot(s) into ${snapshotDir}`)}`)). Ensure the
counter is initialized before processing, incremented only on confirmed success
(not on warnings/errors), and used in the final output so the message reflects
partial failures accurately.
- Line 117: The snapshot writes currently call fs.writeFile(snapshotPath,
serializeDeepnoteSnapshot(...), 'utf-8') and always clobber files; update both
occurrences (the writes using snapshotPath and serializeDeepnoteSnapshot at
lines noted) to honor the CLI --force flag (likely exposed as a boolean like
force or flags.force). Use write options: if force is true use 'w' (or default)
else use { encoding: 'utf-8', flag: 'wx' } so the write will fail if the file
exists, and catch EEXIST to surface a friendly error/exit; ensure both write
sites are changed consistently and any error handling uses the same message.
In `@packages/convert/src/load-runnable-file.ts`:
- Line 43: The .deepnote read currently wraps read failures in
LoadRunnableFileError, breaking the stated contract that filesystem errors
should be propagated unwrapped; in the loadRunnableFile implementation, update
the .deepnote file-read path (the block that currently catches errors at lines
handling ".deepnote") to stop catching and rewrapping raw fs errors—only convert
non-fs/content-validation errors to LoadRunnableFileError (or rethrow after
inspecting error type), and let native fs errors bubble up unchanged so
.deepnote behaves consistently with .ipynb/.py/.qmd; reference
LoadRunnableFileError and the .deepnote read block when making this change.
In `@packages/convert/src/snapshot/split.ts`:
- Around line 33-50: The timestamp parameter is not sanitized causing
path-traversal when interpolated into the filename; update
generateSnapshotFilename to pass timestamp through sanitizeFilenameComponent
(use sanitizeFilenameComponent(timestamp || 'latest')) before building the
basename so the returned string always uses safeSlug, safeProjectId, sanitized
notebookId and sanitized timestamp (preserving the .snapshot.deepnote suffix) to
prevent directory escape; reference generateSnapshotFilename and
sanitizeFilenameComponent when making the change.
In `@packages/mcp/src/tools/execution.ts`:
- Around line 237-250: The selection logic for notebookFilter uses find(...) and
then later relies on engine.runProject filtering by name, which causes ambiguity
when multiple notebooks share the same name; update the block that computes
notebooks (variables: notebookFilter, found, initNotebook,
file.project.notebooks) to detect when notebookFilter matched by name but more
than one notebook has that name and fail fast with an error asking for the
notebook ID (or require using an ID match), instead of proceeding; implement the
same uniqueness guard in the analogous selection code later (the other notebooks
filtering block) so we never pass an ambiguous name to engine.runProject.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 6ed902b2-b607-4a4b-b3dc-71d43525f782
📒 Files selected for processing (25)
packages/cli/src/commands/run.test.tspackages/cli/src/commands/run.tspackages/cli/src/commands/split.test.tspackages/cli/src/commands/split.tspackages/cli/src/integrations/collect-integrations.tspackages/cli/src/utils/format-converter.tspackages/cli/src/utils/output-persistence.test.tspackages/cli/src/utils/output-persistence.tspackages/convert/src/index.tspackages/convert/src/load-runnable-file.tspackages/convert/src/snapshot/index.tspackages/convert/src/snapshot/lookup.test.tspackages/convert/src/snapshot/lookup.tspackages/convert/src/snapshot/resolve-init.test.tspackages/convert/src/snapshot/resolve-init.tspackages/convert/src/snapshot/save-execution-snapshot.tspackages/convert/src/snapshot/snapshot-integration.test.tspackages/convert/src/snapshot/snapshot-notebook-id.tspackages/convert/src/snapshot/split.test.tspackages/convert/src/snapshot/split.tspackages/convert/src/snapshot/types.tspackages/mcp/src/tools/execution.test.tspackages/mcp/src/tools/execution.tspackages/mcp/src/tools/snapshots.test.tspackages/runtime-core/src/execution-engine.ts
💤 Files with no reviewable changes (2)
- packages/convert/src/snapshot/index.ts
- packages/convert/src/index.ts
Flow positively: single-notebook, then composed [init, main], then a single trailing undefined fallback for multi-notebook (legacy) or an unresolvable init. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Integrate PR #408 (shared runnable-file loading & snapshot persistence extracted into @deepnote/convert), which was split out of this branch and merged to main. Resolution principle: adopt main's clean extraction as the base, then re-apply this branch's single-notebook feature work on top. Conflict resolutions (12 files): - convert/load-runnable-file.ts: took main's version (preserves fs ENOENT/EISDIR codes on .deepnote reads; this branch had wrapped them into a code-less error). - convert/snapshot/save-execution-snapshot.ts: kept this branch's feature version (notebookId-scoped filenames, init-notebook composed runs, init result paths). Kept the branch's mergeOutputsIntoFile strip semantics (drops stale per-block executionCount + execution timing so a re-run never leaks a prior run's metadata) and rewrote main's pinning test to assert the strip. - convert/snapshot/split.ts + write-deepnote-file.ts: object-param generateSnapshotFilename (from #408) plus this branch's notebookId + sanitizeFilenameComponent additions. - convert barrels (index.ts, snapshot/index.ts): union of #408 exports and the feature exports (resolve-init, snapshot-notebook-id, SaveExecutionSnapshotOptions). - convert test files: object-form generateSnapshotFilename calls. - cli/format-converter.ts: took main's clean thin wrapper (returns LoadedRunnableFile; dropped re-export shims no consumer needs). - cli/output-persistence.ts: main's thin wrapper + this branch's init options and init result paths; dropped the unused mergeOutputsIntoFile/getSnapshotPath shims. - cli/run.ts + mcp/execution.ts: union imports; kept the init composition (resolveAndComposeInit / resolveRunnableWithInit) while preserving main's fs error-code handling contract. Ran pnpm install (main added a `yaml` dep to @deepnote/database-integrations). Verified green: pnpm typecheck, biome:check, build, test (2330 passed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dinohamzic
left a comment
There was a problem hiding this comment.
Some semi-minor AI findings + an additional comment
- [P2] Snapshot filenames do not round-trip: packages/convert/src/snapshot/lookup.ts:27 ambiguously parses notebook IDs containing an underscore-delimited UUID. ISO timestamps sanitized by generateSnapshotFilename also return
null when parsed. Such snapshots become undiscoverable.
- [P2] Split can leave partial output: packages/cli/src/commands/split.ts:41 writes sequentially before discovering a later collision. Preflight all targets or stage writes before committing them.
- [P2] Directory conversion can create duplicate snapshot identities: packages/cli/src/utils/to-deepnote-conversion.ts:47 shares one project ID while packages/convert/src/jupyter-to-deepnote.ts:100 preserves notebook IDs. Copied
notebooks with the same metadata therefore overwrite each other’s snapshots.
- [P2] Init-prelude documentation is inaccurate: skills/deepnote/references/cli-utility.md:25 says the sibling init runs as a prelude, but --notebook and --block execution filter it out. Qualify the docs or implement prelude
behavior for filtered runs.
| @@ -1 +1 @@ | |||
| # @deepnote/convert | |||
There was a problem hiding this comment.
It seems this README is still referencing deprecated/removed APIs, can you please double check?
dinohamzic
left a comment
There was a problem hiding this comment.
No issues found while testing manually. 👌
- snapshot: normalize the timestamp segment to a '_'-free, parser-readable form in generateSnapshotFilename so raw ISO timestamps no longer misparse the notebook id or produce undiscoverable snapshot filenames. - cli/split: roll back already-written files when a later split collides (without --force), making a failed split all-or-nothing instead of leaving partial output behind. - docs: clarify that filtered runs (--notebook/--block) do not execute the sibling init prelude. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ferences
- Wire the existing convert() outputFormat param into the deepnote-convert
CLI as --output-format (also accepts --outputFormat), validated against the
shared SourceNotebookFormat set, so a .deepnote can be exported to
percent/quarto/marimo from the CLI, not just Jupyter.
- README: correct the removed programmatic API — the to-Deepnote helpers are
convert{Ipynb,Quarto,Percent,Marimo}FileToDeepnoteFile and take a single
file path (not the old plural *FilesToDeepnoteFile with an array) — and
document the --singleFile flag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dinohamzic
left a comment
There was a problem hiding this comment.
-
[P1] Split files lose init context in analysis.
split.tskeeps only the main notebook, while lint/analyze/DAG load it without resolving the sibling init. Dependencies defined in init disappear from the DAG. Make analysis loaders init-aware, as run already is. -
[P1] Copied Jupyter notebooks can overwrite each other's snapshots. Directory conversion shares one project ID (utility) while preserving
deepnote_notebook_id(converter). Duplicate IDs therefore produce identical snapshot paths. Detect and regenerate/reject duplicates. -
[P2] Ordinary
.pyhelpers abort directory conversion.to-deepnote-conversion.tscalls throwingdetectFormat()insidefilter(). Mixed notebook/helper directories fail instead of ignoring unsupported Python files. -
[P2] MCP drops sibling-init divergence warnings.
execution.tsdiscardsresolved.warnings, hiding settings/integration mismatches that CLI reports. -
[P2] Documentation still uses removed APIs and legacy snapshot names.
converting-notebooks.mdimports the removed plural converter;FILES.mdomits the new notebook-ID filename segment.
… docs - P1: resolve sibling init notebooks in lint/analyze/dag/stats like `run` does, via a shared loadAndResolveDeepnoteFile helper, so init-defined symbols no longer disappear from analysis; `run` reuses the shared emitInitResolverWarnings - P2: add non-throwing tryDetectFormat so ordinary .py helpers no longer abort directory conversion (also covers the convert auto-detect loop) - P2: log MCP sibling-init divergence warnings to stderr instead of dropping them - P2: fix docs — singular convertIpynbFileToDeepnoteFile API and the new notebook-ID snapshot filename segment - Extract MCP test fixtures into tools/test-helpers.ts Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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.
Inline comments:
In `@packages/cli/src/commands/analyze.ts`:
- Around line 367-370: The exit-code mapping in analyze.ts should treat
ParseError the same as FileResolutionError and InitNotebookResolutionError,
since loadAndResolveDeepnoteFile can fail with ParseError on malformed .deepnote
files. Update the error instanceof check around the exitCode assignment in
analyze.ts to include ParseError so these failures return ExitCode.InvalidUsage,
and make the same adjustment in dag.ts to keep both command handlers consistent.
🪄 Autofix (Beta)
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: abec5278-721f-4878-8260-41a9e2e1d957
📒 Files selected for processing (22)
FILES.mddocs/converting-notebooks.mdpackages/cli/src/commands/analyze.test.tspackages/cli/src/commands/analyze.tspackages/cli/src/commands/convert.test.tspackages/cli/src/commands/convert.tspackages/cli/src/commands/dag.test.tspackages/cli/src/commands/dag.tspackages/cli/src/commands/lint.test.tspackages/cli/src/commands/lint.tspackages/cli/src/commands/run.tspackages/cli/src/commands/stats.test.tspackages/cli/src/commands/stats.tspackages/cli/src/commands/test-helpers.tspackages/cli/src/utils/load-and-resolve-init.tspackages/cli/src/utils/to-deepnote-conversion.tspackages/convert/src/format-detection.test.tspackages/convert/src/format-detection.tspackages/convert/src/index.tspackages/mcp/src/tools/execution.test.tspackages/mcp/src/tools/execution.tspackages/mcp/src/tools/test-helpers.ts
Malformed .deepnote files can throw ParseError during load; map it to exit code 2 consistently with other file-resolution failures. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Account for PRs that landed on main after the initial bump: - @deepnote/convert 3.2.3 -> 4.0.0 (patch -> MAJOR): #364 and #408 rename/remove many public exports (convertIpynbFilesToDeepnoteFile -> convertIpynbFileToDeepnoteFile, etc.) and switch the .deepnote format to single-notebook-per-file — breaking changes for a published 3.x package. - @deepnote/mcp 0.3.3 -> 0.4.0 (patch -> minor): #364/#408 change tool behavior (single-notebook conversion output, notebook-scoped snapshots), so it is no longer a dependency-bump-only release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: bump package versions Release the changes that have accumulated on main since the last tag for each package: - @deepnote/blocks 4.5.1 -> 4.6.0 - @deepnote/cli 0.6.1 -> 0.7.0 - @deepnote/convert 3.2.2 -> 3.2.3 - @deepnote/database-integrations 1.4.3 -> 1.5.0 - @deepnote/mcp 0.3.2 -> 0.3.3 - @deepnote/reactivity 1.2.0 -> 1.2.1 - @deepnote/runtime-core 0.3.0 -> 0.4.0 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: correct convert (major) and mcp (minor) bumps Account for PRs that landed on main after the initial bump: - @deepnote/convert 3.2.3 -> 4.0.0 (patch -> MAJOR): #364 and #408 rename/remove many public exports (convertIpynbFilesToDeepnoteFile -> convertIpynbFileToDeepnoteFile, etc.) and switch the .deepnote format to single-notebook-per-file — breaking changes for a published 3.x package. - @deepnote/mcp 0.3.3 -> 0.4.0 (patch -> minor): #364/#408 change tool behavior (single-notebook conversion output, notebook-scoped snapshots), so it is no longer a dependency-bump-only release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary by CodeRabbit
deepnote split <path>to generate one-notebook-per-file outputs (with--outputand--force) and included shell completion.--outputFormatto the convert CLI..deepnoteorganization guidance and snapshot/split naming documentation.