feat(local-runner): @deepnote/local-runner — run a .deepnote with edited inputs, locally or in Deepnote Cloud#419
feat(local-runner): @deepnote/local-runner — run a .deepnote with edited inputs, locally or in Deepnote Cloud#419jamesbhobbs wants to merge 62 commits into
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 (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Browser
participant serveStatic
participant runWithInputs
participant ExecutionEngine
participant Snapshot
Browser->>serveStatic: POST /api/run with inputs
serveStatic->>runWithInputs: Run notebook
runWithInputs->>ExecutionEngine: Start and execute project
ExecutionEngine-->>runWithInputs: Outputs and block results
runWithInputs->>Snapshot: Build snapshot YAML
Snapshot-->>serveStatic: Snapshot and summary
serveStatic-->>Browser: JSON execution result
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (5 passed)
Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #419 +/- ##
==========================================
+ Coverage 86.85% 87.25% +0.39%
==========================================
Files 166 180 +14
Lines 8766 9410 +644
Branches 2412 2678 +266
==========================================
+ Hits 7614 8211 +597
- Misses 1151 1198 +47
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
🧹 Nitpick comments (2)
packages/local-runner/src/serve-static.test.ts (2)
10-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
ts-dedentfor the multiline fixture.This raw multiline template literal violates the repository TypeScript rule and makes indentation changes easier to introduce.
As per coding guidelines, TypeScript files should use
ts-dedentfor clean multiline template strings.🤖 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/local-runner/src/serve-static.test.ts` around lines 10 - 31, Replace the raw multiline NOTEBOOK template literal with the repository’s ts-dedent utility, importing it as needed and wrapping the fixture content with dedent so indentation remains clean and consistent.Source: Coding guidelines
71-116: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winAdd regression coverage for rejected-request branches.
Add tests for invalid
inputsshapes, oversized bodies, malformed percent-encoding, and symlink escapes so the server’s validation and traversal guarantees remain enforced.🤖 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/local-runner/src/serve-static.test.ts` around lines 71 - 116, Add regression tests to the serveStatic suite covering rejected requests: invalid `inputs` shapes for POST /api/run, request bodies exceeding the configured size limit, malformed percent-encoded paths, and static-file symlinks escaping the serving root. Use the existing rawStatus/handle helpers and temporary fixture setup as appropriate, asserting the expected 400, 413, 400, and 403 responses respectively.
🤖 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/local-runner/src/run-with-inputs.ts`:
- Around line 67-73: Validate the input’s persistSnapshot setting immediately
after loadDeepnoteFile and before constructing or starting ExecutionEngine;
reject persistSnapshot: true with the existing unsupported-configuration error.
Update the related test to assert validation occurs before engine startup and
that no execution or side effects occur.
In `@packages/local-runner/src/serve-static.ts`:
- Around line 77-86: Validate the parsed request’s inputs before calling runner
in the POST /api/run handler: accept only non-null, non-array objects, return a
400 response for null, arrays, strings, or other invalid values, and pass the
validated record to runner instead of relying on the erased type assertion.
- Around line 106-108: Handle malformed percent-encoding in serveFile by
catching URIError specifically around decodeURIComponent and responding with
HTTP 400, rather than allowing it to reach the outer handler and become a 500;
preserve existing behavior for other errors.
- Around line 115-120: Update the static-file serving logic to validate and read
the target before calling res.writeHead(200), ensuring directories are rejected
and readFile errors are handled by the existing error path without sending
headers first. Use the handler’s target validation and readFile flow around
sendJson and res.end to preserve the 404 response for invalid entries and only
send 200 after a successful file read.
- Around line 106-118: The path-traversal protection in serveFile is only
lexical and allows symlinks under rootDir to escape the serving directory.
Resolve rootDir and target to real paths, then verify the real target remains
within the real root before reading or serving it; return 403 for escapes and
preserve appropriate not-found handling for missing paths.
- Around line 128-137: Update readBody to track received byte length rather than
string length, and explicitly reject with a payload-too-large error when the
limit is exceeded before destroying the request. Handle req.aborted and
req.close events so the Promise cannot remain pending, while avoiding duplicate
settlement; ensure the /api/run caller can identify this limit error and respond
with HTTP 413.
---
Nitpick comments:
In `@packages/local-runner/src/serve-static.test.ts`:
- Around line 10-31: Replace the raw multiline NOTEBOOK template literal with
the repository’s ts-dedent utility, importing it as needed and wrapping the
fixture content with dedent so indentation remains clean and consistent.
- Around line 71-116: Add regression tests to the serveStatic suite covering
rejected requests: invalid `inputs` shapes for POST /api/run, request bodies
exceeding the configured size limit, malformed percent-encoded paths, and
static-file symlinks escaping the serving root. Use the existing
rawStatus/handle helpers and temporary fixture setup as appropriate, asserting
the expected 400, 413, 400, and 403 responses respectively.
🪄 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: f1cdcf9a-f056-41a6-8578-addfd4a80885
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (12)
packages/local-runner/README.mdpackages/local-runner/package.jsonpackages/local-runner/src/apply-input-overrides.test.tspackages/local-runner/src/apply-input-overrides.tspackages/local-runner/src/execution.integration.test.tspackages/local-runner/src/index.tspackages/local-runner/src/load-file.tspackages/local-runner/src/run-with-inputs.test.tspackages/local-runner/src/run-with-inputs.tspackages/local-runner/src/serve-static.test.tspackages/local-runner/src/serve-static.tspackages/local-runner/tsdown.config.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/local-runner/src/run-with-inputs.test.ts (1)
121-128: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAssert persisted snapshot contents, not only file existence.
Read
result.snapshotPathand verify it contains the expected serialized output (or matchesresult.snapshotYaml). This catches write-path and serialization regressions that the current existence checks miss.🤖 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/local-runner/src/run-with-inputs.test.ts` around lines 121 - 128, Update the test “persists a snapshot next to a path input by default (like deepnote run)” to read the file at result.snapshotPath and assert its contents contain the expected serialized output, preferably matching result.snapshotYaml. Keep the existing path and existence assertions while adding validation of the persisted snapshot data.
🤖 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.
Nitpick comments:
In `@packages/local-runner/src/run-with-inputs.test.ts`:
- Around line 121-128: Update the test “persists a snapshot next to a path input
by default (like deepnote run)” to read the file at result.snapshotPath and
assert its contents contain the expected serialized output, preferably matching
result.snapshotYaml. Keep the existing path and existence assertions while
adding validation of the persisted snapshot data.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3907b81a-4ec2-4f6b-8586-a20139bffac5
📒 Files selected for processing (4)
packages/local-runner/README.mdpackages/local-runner/src/run-with-inputs.test.tspackages/local-runner/src/run-with-inputs.tspackages/local-runner/src/serve-static.ts
✅ Files skipped from review due to trivial changes (1)
- packages/local-runner/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/local-runner/src/serve-static.ts
feb76b0 to
ee6e6a7
Compare
85f975b to
7965b78
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
packages/local-runner/src/run-in-cloud.ts (1)
105-121: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReuse the shared input-coercion helper.
coerceInputsrepeats the same input-block lookup andcoerceInputVariableValuecall used byapplyInputOverrides; extracting that shared logic would keep the cloud and local-run paths from drifting.🤖 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/local-runner/src/run-in-cloud.ts` around lines 105 - 121, Extract the input-block lookup and coerceInputVariableValue logic from coerceInputs and applyInputOverrides into a shared helper, then have both callers reuse it while preserving each function’s existing input/output behavior.
🤖 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-cloud.ts`:
- Around line 306-309: Update the defensive refetch in the post-poll handling
around getRun so failures are caught and represented as missing snapshot content
rather than escaping. Preserve the terminal runId and route the failed refetch
through the existing snapshot-error handling path, allowing the command to
render its structured result.
- Around line 214-218: Sanitize the remote runId before interpolating it into
snapshotPath in the fallback write flow. Ensure the resulting filename cannot
contain path separators or traversal segments and remains within the snapshots
directory, using an existing filename-safe identifier or generated name while
preserving the current snapshot extension and write behavior.
In `@packages/local-runner/src/run-in-cloud.ts`:
- Around line 79-91: Update runInCloud to defensively re-fetch the terminal run
with getRun when the successful polled result lacks an inline snapshot, then use
the refreshed run for fetchSnapshotContent and output extraction while
preserving existing failure handling. Add the corresponding getRun mock in
run-in-cloud.test.ts so this path is covered.
---
Nitpick comments:
In `@packages/local-runner/src/run-in-cloud.ts`:
- Around line 105-121: Extract the input-block lookup and
coerceInputVariableValue logic from coerceInputs and applyInputOverrides into a
shared helper, then have both callers reuse it while preserving each function’s
existing input/output behavior.
🪄 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: bdd426d1-356f-4d72-988c-4d2620dde6f7
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (22)
packages/cli/package.jsonpackages/cli/src/commands/run-cloud.tspackages/cloud/README.mdpackages/cloud/package.jsonpackages/cloud/src/cloud-runs.test.tspackages/cloud/src/cloud-runs.tspackages/cloud/src/index.tspackages/cloud/tsdown.config.tspackages/local-runner/README.mdpackages/local-runner/package.jsonpackages/local-runner/src/apply-input-overrides.test.tspackages/local-runner/src/apply-input-overrides.tspackages/local-runner/src/execution.integration.test.tspackages/local-runner/src/index.tspackages/local-runner/src/load-file.tspackages/local-runner/src/run-in-cloud.test.tspackages/local-runner/src/run-in-cloud.tspackages/local-runner/src/run-with-inputs.test.tspackages/local-runner/src/run-with-inputs.tspackages/local-runner/src/serve-static.test.tspackages/local-runner/src/serve-static.tspackages/local-runner/tsdown.config.ts
✅ Files skipped from review due to trivial changes (2)
- packages/cloud/README.md
- packages/cloud/tsdown.config.ts
🚧 Files skipped from review as they are similar to previous changes (10)
- packages/local-runner/src/execution.integration.test.ts
- packages/local-runner/src/apply-input-overrides.test.ts
- packages/local-runner/tsdown.config.ts
- packages/local-runner/src/serve-static.test.ts
- packages/local-runner/src/index.ts
- packages/local-runner/src/apply-input-overrides.ts
- packages/local-runner/src/run-with-inputs.ts
- packages/local-runner/README.md
- packages/local-runner/src/run-with-inputs.test.ts
- packages/local-runner/src/load-file.ts
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 3
🧹 Nitpick comments (1)
packages/local-runner/src/run-in-cloud.ts (1)
105-121: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReuse the shared input-coercion helper.
coerceInputsrepeats the same input-block lookup andcoerceInputVariableValuecall used byapplyInputOverrides; extracting that shared logic would keep the cloud and local-run paths from drifting.🤖 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/local-runner/src/run-in-cloud.ts` around lines 105 - 121, Extract the input-block lookup and coerceInputVariableValue logic from coerceInputs and applyInputOverrides into a shared helper, then have both callers reuse it while preserving each function’s existing input/output behavior.
🤖 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-cloud.ts`:
- Around line 306-309: Update the defensive refetch in the post-poll handling
around getRun so failures are caught and represented as missing snapshot content
rather than escaping. Preserve the terminal runId and route the failed refetch
through the existing snapshot-error handling path, allowing the command to
render its structured result.
- Around line 214-218: Sanitize the remote runId before interpolating it into
snapshotPath in the fallback write flow. Ensure the resulting filename cannot
contain path separators or traversal segments and remains within the snapshots
directory, using an existing filename-safe identifier or generated name while
preserving the current snapshot extension and write behavior.
In `@packages/local-runner/src/run-in-cloud.ts`:
- Around line 79-91: Update runInCloud to defensively re-fetch the terminal run
with getRun when the successful polled result lacks an inline snapshot, then use
the refreshed run for fetchSnapshotContent and output extraction while
preserving existing failure handling. Add the corresponding getRun mock in
run-in-cloud.test.ts so this path is covered.
---
Nitpick comments:
In `@packages/local-runner/src/run-in-cloud.ts`:
- Around line 105-121: Extract the input-block lookup and
coerceInputVariableValue logic from coerceInputs and applyInputOverrides into a
shared helper, then have both callers reuse it while preserving each function’s
existing input/output behavior.
🪄 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: bdd426d1-356f-4d72-988c-4d2620dde6f7
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (22)
packages/cli/package.jsonpackages/cli/src/commands/run-cloud.tspackages/cloud/README.mdpackages/cloud/package.jsonpackages/cloud/src/cloud-runs.test.tspackages/cloud/src/cloud-runs.tspackages/cloud/src/index.tspackages/cloud/tsdown.config.tspackages/local-runner/README.mdpackages/local-runner/package.jsonpackages/local-runner/src/apply-input-overrides.test.tspackages/local-runner/src/apply-input-overrides.tspackages/local-runner/src/execution.integration.test.tspackages/local-runner/src/index.tspackages/local-runner/src/load-file.tspackages/local-runner/src/run-in-cloud.test.tspackages/local-runner/src/run-in-cloud.tspackages/local-runner/src/run-with-inputs.test.tspackages/local-runner/src/run-with-inputs.tspackages/local-runner/src/serve-static.test.tspackages/local-runner/src/serve-static.tspackages/local-runner/tsdown.config.ts
✅ Files skipped from review due to trivial changes (2)
- packages/cloud/README.md
- packages/cloud/tsdown.config.ts
🚧 Files skipped from review as they are similar to previous changes (10)
- packages/local-runner/src/execution.integration.test.ts
- packages/local-runner/src/apply-input-overrides.test.ts
- packages/local-runner/tsdown.config.ts
- packages/local-runner/src/serve-static.test.ts
- packages/local-runner/src/index.ts
- packages/local-runner/src/apply-input-overrides.ts
- packages/local-runner/src/run-with-inputs.ts
- packages/local-runner/README.md
- packages/local-runner/src/run-with-inputs.test.ts
- packages/local-runner/src/load-file.ts
🛑 Comments failed to post (3)
packages/cli/src/commands/run-cloud.ts (2)
214-218: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
rg -n 'runId|NormalizedRun|triggerNotebookRun|getRun' packages/cloud packages/cliRepository: deepnote/deepnote
Length of output: 7747
🏁 Script executed:
#!/bin/bash set -euo pipefail echo '--- outline: packages/cli/src/commands/run-cloud.ts ---' ast-grep outline packages/cli/src/commands/run-cloud.ts --view expanded || true echo echo '--- lines 200-230 ---' sed -n '200,230p' packages/cli/src/commands/run-cloud.ts echo echo '--- lines 288-320 ---' sed -n '288,320p' packages/cli/src/commands/run-cloud.ts echo echo '--- relevant tests around snapshot path ---' sed -n '250,340p' packages/cli/src/commands/run-cloud.test.tsRepository: deepnote/deepnote
Length of output: 7873
Sanitize
runIdbefore using it in the fallback filename.runIdcomes from the remote response, so a path separator or..segment can escape./snapshots; use a filename-safe ID or a generated name here.🧰 Tools
🪛 ast-grep (0.44.1)
[warning] 217-217: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFile(snapshotPath, bytes, 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').(detect-non-literal-fs-filename-typescript)
🤖 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-cloud.ts` around lines 214 - 218, Sanitize the remote runId before interpolating it into snapshotPath in the fallback write flow. Ensure the resulting filename cannot contain path separators or traversal segments and remains within the snapshots directory, using an existing filename-safe identifier or generated name while preserving the current snapshot extension and write behavior.Source: Linters/SAST tools
306-309: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Do not let the defensive refetch bypass post-run handling.
If
getRunfails after polling has already returned a terminalrunId, the exception escapes before the command renders its structured result. Treat the refetch failure as missing snapshot content and let the existing snapshot-error path handle it.🤖 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-cloud.ts` around lines 306 - 309, Update the defensive refetch in the post-poll handling around getRun so failures are caught and represented as missing snapshot content rather than escaping. Preserve the terminal runId and route the failed refetch through the existing snapshot-error handling path, allowing the command to render its structured result.packages/local-runner/src/run-in-cloud.ts (1)
79-91: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Missing defensive re-fetch when the terminal run lacks an inline snapshot.
packages/cli/src/commands/run-cloud.tshits this exact scenario and explicitly re-fetches viagetRunwhen!finalRun.snapshotafter polling, with the comment noting some deployments only attach the snapshot once terminal.runInCloudskips that guard —fetchSnapshotContentreturnsnullimmediately whenrun.snapshotis absent, so a genuinely successful run can silently come back withsuccess: true,outputs: [],snapshotYaml: null.🔧 Proposed fix: mirror the CLI's defensive re-fetch
import { describeRunError, fetchSnapshotContent, + getRun, isSuccessStatus, type PollOptions, pollRunUntilComplete, triggerNotebookRun, } from '`@deepnote/cloud`' @@ - const run = await pollRunUntilComplete(baseUrl, token, started.runId, { snapshotDelivery: 'inline', ...options.poll }) + let run = await pollRunUntilComplete(baseUrl, token, started.runId, { snapshotDelivery: 'inline', ...options.poll }) + // Some deployments only attach the snapshot once the run is confirmed terminal. + if (!run.snapshot) { + run = await getRun(baseUrl, token, run.runId, { snapshotDelivery: 'inline' }) + } const success = isSuccessStatus(run.status)Note:
run-in-cloud.test.ts'svi.mock('@deepnote/cloud', ...)will need to add agetRunmock for this path to be testable.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.let run = await pollRunUntilComplete(baseUrl, token, started.runId, { snapshotDelivery: 'inline', ...options.poll }) if (!run.snapshot) { run = await getRun(baseUrl, token, run.runId, { snapshotDelivery: 'inline' }) } const success = isSuccessStatus(run.status) const snapshotYaml = success ? await fetchSnapshotContent(run, { baseUrl, token }) : null return { runId: run.runId, status: run.status, success, outputs: snapshotYaml ? extractOutputs(snapshotYaml) : [], snapshotYaml, error: success ? undefined : describeRunError(run), }🤖 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/local-runner/src/run-in-cloud.ts` around lines 79 - 91, Update runInCloud to defensively re-fetch the terminal run with getRun when the successful polled result lacks an inline snapshot, then use the refreshed run for fetchSnapshotContent and output extraction while preserving existing failure handling. Add the corresponding getRun mock in run-in-cloud.test.ts so this path is covered.Source: Linked repositories
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
packages/local-runner/src/run-in-cloud.test.ts (1)
127-140: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNo test covers domain/baseUrl propagation into the upload fallback.
Given the upload-if-missing path silently drops any custom
baseUrl/domain (seeopen-in-cloud.ts), a test assertinguploadNotebookreceives the expected domain would catch that regression.🤖 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/local-runner/src/run-in-cloud.test.ts` around lines 127 - 140, Extend the upload-if-missing test around runInCloud to pass a custom baseUrl/domain and assert cloudMock.uploadNotebook receives that value. Preserve the existing missing-notebook result assertions while verifying domain propagation through the fallback path.packages/cloud/src/import.ts (1)
49-49: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueNo runtime validation of the init response shape.
initis blindly cast (as InitImportResponse) from JSON; a malformed/renamed field on the server side would surface as an opaque failure later (e.g.fetch(init.uploadUrl, ...)withundefined). Consider a minimal shape check before use.🤖 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/cloud/src/import.ts` at line 49, Validate the parsed response in the import initialization flow before using it as InitImportResponse. Add a minimal runtime check that confirms the required initialization fields, especially uploadUrl, exist and have the expected shape, and fail immediately with a clear error when validation fails; keep subsequent fetch usage unchanged for valid responses.
🤖 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/cloud/src/import.ts`:
- Around line 51-55: Update the presigned upload request in the import flow to
pass an AbortSignal timeout using the existing timeout configuration, matching
the init POST behavior. Ensure the PUT fetch for fileBytes cannot block
indefinitely while preserving its current method, headers, and body.
In `@packages/local-runner/src/open-in-cloud.ts`:
- Around line 21-30: The runInCloud not-found fallback drops the custom domain
when delegating to openInCloud, causing uploads to use the default endpoint.
Update the fallback call in run-in-cloud.ts to pass the domain derived from
baseUrl alongside inputs, and add coverage in
packages/local-runner/src/run-in-cloud.test.ts for a custom baseUrl/domain
asserting uploadNotebook receives that domain; the openInCloud function already
forwards options.domain and requires no direct change.
---
Nitpick comments:
In `@packages/cloud/src/import.ts`:
- Line 49: Validate the parsed response in the import initialization flow before
using it as InitImportResponse. Add a minimal runtime check that confirms the
required initialization fields, especially uploadUrl, exist and have the
expected shape, and fail immediately with a clear error when validation fails;
keep subsequent fetch usage unchanged for valid responses.
In `@packages/local-runner/src/run-in-cloud.test.ts`:
- Around line 127-140: Extend the upload-if-missing test around runInCloud to
pass a custom baseUrl/domain and assert cloudMock.uploadNotebook receives that
value. Preserve the existing missing-notebook result assertions while verifying
domain propagation through the fallback path.
🪄 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: 19b9d0df-5044-49f6-a7ce-a8a099ebc629
📒 Files selected for processing (7)
packages/cloud/src/import.tspackages/cloud/src/index.tspackages/local-runner/src/index.tspackages/local-runner/src/open-in-cloud.tspackages/local-runner/src/run-in-cloud.test.tspackages/local-runner/src/run-in-cloud.tspackages/local-runner/src/serve-static.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/local-runner/src/index.ts
- packages/local-runner/src/run-in-cloud.ts
- packages/local-runner/src/serve-static.ts
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/cloud/src/projects.ts`:
- Around line 58-66: Update findNotebookId so that when query.notebookName is
provided, it only returns a notebook with that exact name while iterating
through projects; continue searching older projects if the current project has
no match. Retain the fallback to the first notebook only when no notebookName
was requested.
🪄 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: 4d2cc5dc-e023-4402-b3fe-2fd19a2d504c
📒 Files selected for processing (5)
packages/cloud/src/cloud-runs.tspackages/cloud/src/index.tspackages/cloud/src/projects.tspackages/local-runner/src/run-in-cloud.test.tspackages/local-runner/src/run-in-cloud.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/cloud/src/index.ts
- packages/local-runner/src/run-in-cloud.test.ts
- packages/local-runner/src/run-in-cloud.ts
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/cloud/src/projects.ts (1)
63-76: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winFallback to
notebooks[0]still short-circuits the search for an exactnotebookNamematch.When
query.notebookNameis provided but absent from the current project,match ?? notebooks[0]falls back to that project's first notebook and returns immediately — it never checks older matching projects for the exact name. This is the same behavior flagged in a prior review (marked "Addressed"), but the current code still exhibits it.🐛 Proposed fix
- for (const project of projects) { - const notebooks = project.notebooks ?? [] - const match = query.notebookName ? notebooks.find(notebook => notebook.name === query.notebookName) : undefined - const notebook = match ?? notebooks[0] - if (notebook) { - return { notebookId: notebook.id, projectId: project.id } - } - } - return undefined + if (query.notebookName) { + for (const project of projects) { + const match = (project.notebooks ?? []).find(notebook => notebook.name === query.notebookName) + if (match) { + return { notebookId: match.id, projectId: project.id } + } + } + return undefined + } + const first = projects[0]?.notebooks?.[0] + return first ? { notebookId: first.id, projectId: projects[0].id } : undefined🤖 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/cloud/src/projects.ts` around lines 63 - 76, Update the project/notebook search loop so that when query.notebookName is provided, projects without an exact notebook match are skipped rather than falling back to notebooks[0]; return the matching notebook from any matching project, while retaining the first-notebook fallback only when no notebook name is requested.
🧹 Nitpick comments (1)
examples/local-runner-demo/index.html (1)
163-168: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low valueAvoid
innerHTMLfor the view-URL link.
viewUrlis server-controlled today, but building viainnerHTMLtemplate literal is an easy-to-copy anti-pattern. Building the anchor via DOM APIs sidesteps it entirely.🔧 Proposed fix
- if (data.viewUrl) { - const s = $('`#status`'); s.className = 'status ok' - s.innerHTML = `☁ cloud run ${data.status} — <a href="${data.viewUrl}" target="_blank" rel="noopener">view runs in Deepnote →</a>` - } else { + if (data.viewUrl) { + const s = $('`#status`'); s.className = 'status ok' + s.textContent = `☁ cloud run ${data.status} — ` + const a = el('a'); a.href = data.viewUrl; a.target = '_blank'; a.rel = 'noopener'; a.textContent = 'view runs in Deepnote →' + s.appendChild(a) + } else {🤖 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 `@examples/local-runner-demo/index.html` around lines 163 - 168, Replace the innerHTML assignment in the data.viewUrl branch with DOM API construction: clear the status element, create an anchor element, set its text, href, target, and rel properties, then append the surrounding status text and anchor while preserving the existing status class and displayed message.Source: Linters/SAST tools
🤖 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 `@examples/local-runner-demo/index.html`:
- Around line 190-201: Update the text/html branch in renderOutput to sanitize
notebook HTML before assigning it to innerHTML, using an established sanitizer
if available and preserving rich HTML rendering. Ensure untrusted or shared
notebook output cannot execute scripts or otherwise inject unsafe content.
---
Duplicate comments:
In `@packages/cloud/src/projects.ts`:
- Around line 63-76: Update the project/notebook search loop so that when
query.notebookName is provided, projects without an exact notebook match are
skipped rather than falling back to notebooks[0]; return the matching notebook
from any matching project, while retaining the first-notebook fallback only when
no notebook name is requested.
---
Nitpick comments:
In `@examples/local-runner-demo/index.html`:
- Around line 163-168: Replace the innerHTML assignment in the data.viewUrl
branch with DOM API construction: clear the status element, create an anchor
element, set its text, href, target, and rel properties, then append the
surrounding status text and anchor while preserving the existing status class
and displayed message.
🪄 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: d12ba3d8-86cc-491a-ac0a-04b7cc28e57c
📒 Files selected for processing (7)
examples/local-runner-demo/README.mdexamples/local-runner-demo/index.htmlexamples/local-runner-demo/serve.mjspackages/cloud/src/projects.tspackages/local-runner/src/run-in-cloud.test.tspackages/local-runner/src/run-in-cloud.tspackages/local-runner/src/serve-static.ts
✅ Files skipped from review due to trivial changes (1)
- examples/local-runner-demo/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/local-runner/src/run-in-cloud.test.ts
- packages/local-runner/src/run-in-cloud.ts
- packages/local-runner/src/serve-static.ts
0ab08e1 to
a86964c
Compare
`deepnote run -i <slider>=N` silently dropped the execution snapshot: the
override value was written to `deepnote_variable_value` verbatim (a number),
but the block schema requires a string, so `serializeDeepnoteSnapshot` threw
`Expected string, received number` and the best-effort save swallowed it.
Add a type-aware `coerceInputVariableValue(block, value)` schema-normalization
helper to @deepnote/blocks (slider/text/textarea/date/file → string; checkbox
strict boolean; select shape-only respecting multi-value; date-range arity),
and apply it in the CLI's `applyInputOverrides`. The kernel-injection payload
passed to `runProject({ inputs })` intentionally keeps native user values.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ee6e6a7 to
ae895f2
Compare
Adds a `--cloud` flag to the `run` command that triggers a cloud run of an
existing Deepnote notebook via the public API (`POST /v2/runs`), polls it to
completion (`GET /v2/runs/{runId}`), and downloads the resulting snapshot into
the local `snapshots/` convention — so `deepnote diff` and the MCP snapshot
tools work on it immediately.
The notebook to run is resolved from `--notebook-id`, else the local
`.deepnote` file's notebook (`--notebook <name>` or the single/main notebook).
The notebook must already exist in Deepnote; uploading local content
(`--push`) is a hidden, not-yet-implemented follow-up.
- New `utils/cloud-runs.ts`: Bearer-auth client mirroring `fetchIntegrations`
with drift-tolerant polling (429/5xx backoff, per-request + total timeout,
`RunTimeoutError` carrying the runId) and cross-origin-safe snapshot download
(no bearer on presigned S3 URLs).
- New `commands/run-cloud.ts`: orchestration — notebook-id resolution, `.env`
load, blank-token-as-missing, snapshot parse (snapshot-doc → full-file split
→ raw fallback), timestamped + latest writes, terminal-failure → exit 1 while
preserving runId/status/snapshotPath, `-o json`/`-o toon` output.
- `utils/parse-inputs.ts`: extracted shared helper (avoids a run↔run-cloud cycle).
- `run.ts` / `cli.ts`: options, early dispatch, incompatible-flag guard, help.
- Docs: `skills/deepnote/references/cli-run.md` + `packages/cli/README.md`.
The runs API is in preview, so response schemas are intentionally permissive
and the exact snapshot field names / `detached` requirement should be confirmed
against a live token.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… origins Cap per-request timeout, backoff, and interval sleeps to the remaining deadline so `timeoutMs` is a true total wait, and re-check the deadline before each request. Compare URL origins (not hosts) before attaching the bearer token to a snapshot download, so a same-host http:// URL is treated as cross-origin. (CodeRabbit) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e saved Downloading the snapshot is the command's contract, so a successful run whose snapshot is missing, unretrievable, or unwritable now exits 1 with success:false and an error (rather than best-effort success). A run that already failed keeps a missing snapshot non-fatal. (CodeRabbit) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These can hold secrets or PII and were emitted to stderr in debug mode; log only presence/count now, keeping token redaction. (CodeRabbit) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Validate the whole string and require a safe positive integer so `1.5` and `10s` are rejected instead of silently truncated. (CodeRabbit) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…re for cloud runs Use the full `https://api.deepnote.com` default in the README, note that `-o llm` resolves to `toon`, and document that a successful run whose snapshot can't be saved exits 1. (CodeRabbit) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/cloud package
Move the Deepnote Cloud runs client (trigger/poll/get/fetch-snapshot) out of
cli/utils/cloud-runs.ts into a new @deepnote/cloud package so it can be shared,
and fix snapshot fetching along the way: GET /v2/runs/{runId} returns the
snapshot on flat run.snapshotContent / run.snapshotDownloadUrl fields, not
nested under `snapshot`, so normalizeRun now reads them and `deepnote run
--cloud` actually retrieves outputs. run-cloud imports from @deepnote/cloud;
parseApiErrorMessage is inlined so the package has no CLI dependency.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
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/run.ts (1)
583-599: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winDuplicate
applyInputOverridesimplementation.This function is functionally identical to
applyInputOverridesinpackages/local-runner/src/apply-input-overrides.ts(same loop, same guard, samecoerceInputVariableValuecall, near-identical comments). Two independent copies of the same coercion loop will silently drift if either package extends this logic later (e.g. new guard conditions, error handling).Consider exporting a single
applyInputOverridesfrom@deepnote/blocks(which already hostscoerceInputVariableValue) and having bothpackages/cli/src/commands/run.tsandpackages/local-runner/src/apply-input-overrides.tsimport it, rather than maintaining two copies.🤖 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 583 - 599, Consolidate the duplicate applyInputOverrides implementations by exporting a shared applyInputOverrides from `@deepnote/blocks` alongside coerceInputVariableValue. Replace the local loop in both run.ts and local-runner’s apply-input-overrides.ts with imports of that shared function, preserving the existing metadata override and coercion behavior.
🤖 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 `@examples/local-runner-demo/index.html`:
- Around line 163-168: Replace the template-literal assignment to s.innerHTML in
the data.viewUrl branch with DOM API construction: create the anchor element,
set its href and text content, apply target and rel attributes, and append it
alongside the status text. Preserve the existing status styling and displayed
cloud-run status without interpolating data.viewUrl or other dynamic values into
HTML.
---
Outside diff comments:
In `@packages/cli/src/commands/run.ts`:
- Around line 583-599: Consolidate the duplicate applyInputOverrides
implementations by exporting a shared applyInputOverrides from `@deepnote/blocks`
alongside coerceInputVariableValue. Replace the local loop in both run.ts and
local-runner’s apply-input-overrides.ts with imports of that shared function,
preserving the existing metadata override and coercion behavior.
🪄 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: eadfe3fe-2486-4824-9300-be75e4e5905b
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (33)
examples/local-runner-demo/README.mdexamples/local-runner-demo/index.htmlexamples/local-runner-demo/serve.mjspackages/blocks/src/blocks/input-blocks.test.tspackages/blocks/src/blocks/input-blocks.tspackages/blocks/src/index.tspackages/cli/package.jsonpackages/cli/src/commands/run-cloud.tspackages/cli/src/commands/run.test.tspackages/cli/src/commands/run.tspackages/cloud/README.mdpackages/cloud/package.jsonpackages/cloud/src/cloud-runs.test.tspackages/cloud/src/cloud-runs.tspackages/cloud/src/import.tspackages/cloud/src/index.tspackages/cloud/src/projects.tspackages/cloud/tsdown.config.tspackages/local-runner/README.mdpackages/local-runner/package.jsonpackages/local-runner/src/apply-input-overrides.test.tspackages/local-runner/src/apply-input-overrides.tspackages/local-runner/src/execution.integration.test.tspackages/local-runner/src/index.tspackages/local-runner/src/load-file.tspackages/local-runner/src/open-in-cloud.tspackages/local-runner/src/run-in-cloud.test.tspackages/local-runner/src/run-in-cloud.tspackages/local-runner/src/run-with-inputs.test.tspackages/local-runner/src/run-with-inputs.tspackages/local-runner/src/serve-static.test.tspackages/local-runner/src/serve-static.tspackages/local-runner/tsdown.config.ts
✅ Files skipped from review due to trivial changes (7)
- packages/blocks/src/index.ts
- packages/cloud/package.json
- packages/cloud/src/index.ts
- packages/cloud/README.md
- packages/cli/src/commands/run-cloud.ts
- examples/local-runner-demo/README.md
- packages/local-runner/README.md
🚧 Files skipped from review as they are similar to previous changes (21)
- examples/local-runner-demo/serve.mjs
- packages/local-runner/src/execution.integration.test.ts
- packages/cloud/tsdown.config.ts
- packages/cli/package.json
- packages/local-runner/src/load-file.ts
- packages/local-runner/package.json
- packages/local-runner/src/index.ts
- packages/local-runner/src/apply-input-overrides.test.ts
- packages/local-runner/src/open-in-cloud.ts
- packages/cloud/src/import.ts
- packages/local-runner/tsdown.config.ts
- packages/local-runner/src/run-with-inputs.test.ts
- packages/local-runner/src/apply-input-overrides.ts
- packages/cloud/src/cloud-runs.ts
- packages/local-runner/src/serve-static.test.ts
- packages/cloud/src/projects.ts
- packages/local-runner/src/run-in-cloud.ts
- packages/local-runner/src/run-with-inputs.ts
- packages/local-runner/src/serve-static.ts
- packages/local-runner/src/run-in-cloud.test.ts
- packages/cloud/src/cloud-runs.test.ts
a86964c to
f8e6c67
Compare
Addresses CodeRabbit review on #417: - A path-like `runId` from the runs API could escape ./snapshots when used in the fallback filename via resolve(); sanitize it (allow [A-Za-z0-9_-], cap length) and build the path with join(). Adds a regression test with a malicious `../` runId. - Assert the cross-origin snapshot download omits the Authorization header (the presigned S3 URL is a different origin; the bearer must not leak). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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 (2)
examples/local-runner/run-app/README.md (2)
59-61: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftSandboxing here is not a full trust boundary
examples/local-runner/run-app/README.md:59-61The null-origin sandbox blocks parent DOM access, but
allow-scriptsstill lets notebook HTML execute and make outbound requests. If this output is untrusted, sanitize/strip scripts or use a resize path that doesn’t require script execution.🤖 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 `@examples/local-runner/run-app/README.md` around lines 59 - 61, Update the sandboxed iframe guidance in the README to avoid presenting sandboxing as a complete trust boundary. Ensure untrusted notebook HTML cannot execute scripts or make outbound requests by sanitizing/stripping scripts, or replace the script-dependent resize mechanism with a non-executing approach while preserving automatic height fitting.
62-63: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winFix the README wording. The slider is passed to the kernel as native
7; only the stored metadata stays'7'. This line says the opposite.🤖 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 `@examples/local-runner/run-app/README.md` around lines 62 - 63, Update the README wording near the input coercion description to state that slider input is passed to the kernel and snapshot as native 7, while only the stored metadata remains '7'. Remove the claim that the kernel and snapshot receive the string value.
🤖 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 `@examples/local-runner-showcase.deepnote`:
- Line 302: Update the executive readout prompt in the local runner showcase to
reference the selected reporting window or configured reporting period instead
of “this quarter,” while preserving the request for a short sales-lead summary.
- Around line 313-314: Update the prompt construction around the analyst notes
so raw user-editable analyst_notes are never interpolated into the agent’s
instruction block. Remove the direct inclusion or replace it with a constrained,
validated summary/schema while preserving the computed numeric values and
existing instruction flow.
---
Outside diff comments:
In `@examples/local-runner/run-app/README.md`:
- Around line 59-61: Update the sandboxed iframe guidance in the README to avoid
presenting sandboxing as a complete trust boundary. Ensure untrusted notebook
HTML cannot execute scripts or make outbound requests by sanitizing/stripping
scripts, or replace the script-dependent resize mechanism with a non-executing
approach while preserving automatic height fitting.
- Around line 62-63: Update the README wording near the input coercion
description to state that slider input is passed to the kernel and snapshot as
native 7, while only the stored metadata remains '7'. Remove the claim that the
kernel and snapshot receive the string value.
🪄 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: e836c046-8257-4314-be75-79ff1177c193
📒 Files selected for processing (4)
examples/local-runner-showcase.deepnoteexamples/local-runner/README.mdexamples/local-runner/run-app/README.mdexamples/local-runner/run-app/serve.mjs
🚧 Files skipped from review as they are similar to previous changes (2)
- examples/local-runner/run-app/serve.mjs
- examples/local-runner/README.md
Running a notebook that wasn't in Deepnote yet took two attempts and a browser: runInCloud uploaded the file via /v1/import, returned `needs-open` + a launchUrl, and you finished the import by hand before running again. That flow exists because /v1/import is unauthenticated — it sends no bearer token, so only a logged-in browser session can say which workspace to import into. But runInCloud already requires a token and throws without one. It is authenticated by definition, so it had nothing to gain from the browser and was simply borrowing the mechanism `deepnote open` needs. Add createProject to @deepnote/cloud — POST /v2/projects, /v2/notebooks, /v2/blocks against the authenticated public API — and have runInCloud create-and-run in a single call, reporting `created: true`. The `needs-open` status and `launchUrl` field are gone rather than deprecated: the package is unpublished, so this is free now and a breaking change later. It takes a plain ProjectSpec, not a DeepnoteFile, so @deepnote/cloud stays a thin client with no domain types — and `deepnote run --cloud --push`, still unimplemented on main, can build on this rather than reinvent it. Two API details it has to absorb: - POST /v2/projects seeds a project with an empty placeholder notebook, and nothing can rename one. It creates ours, then deletes the placeholders — in that order, so a project is never momentarily empty, and best-effort, so a stray one is a warning rather than a failed create. - There is no bulk block endpoint, so blocks are one sequential request each to keep `position` meaningful. onProgress exists because 16 blocks is 16 round-trips before the run even starts. `deepnote open`, the /v1 import client, and uploadNotebook are untouched — the unauthenticated browser flow is still the right one when there is no token. Verified against the live API from an empty workspace: created 16 blocks and ran them in one call (created: true, success, 47.9s), inputs applied, agent block executed, and no placeholder notebook left behind. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The run-app knew nothing about a notebook's past runs, so a page sitting next
to a workspace full of successful runs still opened on "Edit the inputs, then
Run" — with the last run's outputs one fetch away.
Add listNotebookRuns and getCloudRun (@deepnote/cloud and @deepnote/local-runner
respectively), expose them as GET /api/cloud-runs and /api/cloud-runs/{runId},
and give the run-app a Cloud runs sidebar. It loads the newest successful run's
snapshot on open, and each row loads that run's outputs — read from the snapshot,
not re-executed.
The history is the notebook's real one, so runs started from the Deepnote UI show
up here too, not just runs from this page.
The two routes fail differently, on purpose. Listing answers `{ runs: [] }` when
there's no token or the notebook was never pushed: both are ordinary states for a
demo, not errors. Asking for a specific run by id and not getting it has no
sensible empty state, so it 502s.
Deepnote exposes no per-run URL — GET /v2/notebooks/{id}/runs returns runId,
status, createdAt, completedAt and nothing linkable. Rather than have every row
open the same page, rows load their own snapshot locally and the one external
link lives in the header.
Only cloud runs appear: the run-app runs locally with persistSnapshot: false, so
local runs leave no snapshot to list.
Verified against the live API: the history lists real runs, and the newest one
renders 7 output blocks from its 105KB snapshot.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
run-in-cloud.ts had grown to 432 lines and three public functions, while every
other file in the package is one concern. I put listCloudRuns and getCloudRun
there because they reused its private helpers — a smaller diff at the cost of the
package's convention. Reading a notebook's run history is not running a notebook.
- cloud-runs.ts — listCloudRuns + getCloudRun; nothing here executes anything
- cloud-common.ts — what both entry points share: token/base-URL resolution,
locating a notebook, and reading a snapshot into outputs
- run-in-cloud.ts — back to one public function (245 lines)
extractOutputs went to cloud-common rather than snapshot-view, despite looking
like a snapshot concern: its own docstring says "a cloud snapshot", nothing but
the cloud paths call it, and snapshot-view.ts is deliberately Node-free so it can
bundle for the browser via browser.ts. Even a type-only edge to run-with-inputs
is a tripwire for whoever refactors it next.
The token check was duplicated three times over; requireToken() now owns it and
names the caller in the error.
No behavior change. cloud-runs.ts also gets the direct unit tests these two never
had — they were only covered through serve-static's fakes.
Verified live, since a refactor is only as good as its behavior: from an empty
workspace, listCloudRuns returned the empty state, runInCloud created and ran
(created: true, success), listCloudRuns then found the run and built its viewUrl,
and getCloudRun read back 7 output blocks from an 85KB snapshot with the input
applied and the agent block executed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review findings on the headless-create change, two of them real bugs it introduced: A swallowed lookup error became destructive. findNotebook was wrapped in .catch(() => undefined), so any failure read as "not in Deepnote". That was harmless when the fallback was a launchUrl; now the fallback creates a project, so a transient /v2/projects blip would silently litter the workspace with duplicates. Only a lookup that succeeds and finds nothing means absence — a failed lookup is now just a failure. Targeted runs ran the wrong blocks. --block ids come from the local file, but a created notebook has ids Deepnote assigned, so they addressed nothing there. createProject returns block ids in the order it was given them, so source blocks map onto cloud ids positionally. An id that doesn't map throws: a targeted run that quietly ran something else is worse than one that fails. Also: - The showcase agent prompt said "this quarter" while trailing_months spans 3-12, and told the agent to "take the analyst notes into account" — free text a user types. That is an injection surface: the notebook escapes that same input for HTML but the agent path had no equivalent care. The notes are now framed as commentary to weigh, never instructions. - The run history rendered a failed past run as green "Showing a cloud run" over an empty canvas, which reads as success. It reports the failure now. - Docs caught up with the headless change: the package README still said cloud runs need a notebook that already exists. The run-app README claimed output "can never touch this page" — it can postMessage, which is exactly how the height fix works, hence the origin+source check — and its slider example read backwards about which side holds the string. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/local-runner/src/run-in-cloud.ts (2)
82-88: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winSeparate the source notebook ID from the Cloud notebook ID.
When
options.notebookIdis a Cloud-assigned ID absent from the file,coerceInputscannot find the local input blocks, so schema coercion is skipped. Track a local source notebook ID separately for coercion and creation.🤖 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/local-runner/src/run-in-cloud.ts` around lines 82 - 88, Separate the Cloud notebook ID from the local source notebook ID in the run flow. Use a locally resolved notebook ID from resolveNotebookId(file) when calling coerceInputs and when creating or locating the source notebook, while preserving options.notebookId for Cloud API operations that require the assigned ID.
99-116: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winDo not treat every lookup failure as “not found.”
The blanket
.catch(() => undefined)converts authentication, network, and server failures into permission to create a duplicate project. Let lookup errors propagate; reserve creation for a successful lookup returning no match.- }).catch(() => undefined) + })🤖 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/local-runner/src/run-in-cloud.ts` around lines 99 - 116, Update the findNotebook call in the run-in-cloud flow to stop swallowing lookup errors; remove the blanket catch so authentication, network, and server failures propagate. Keep the existing creation branch gated only on a successful lookup returning no result.
🤖 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 `@examples/local-runner/run-app/index.html`:
- Around line 328-338: Prevent stale asynchronous responses from updating the UI
by adding request-generation tracking (or aborting superseded requests). In
examples/local-runner/run-app/index.html lines 328-338, guard snapshot rendering
in showRun with the latest display-request generation; in lines 284-288, ignore
history responses older than the latest history request; and in lines 365-376,
invalidate or supersede pending snapshot renders when starting a fresh run.
In `@packages/cloud/src/cloud-runs.ts`:
- Around line 309-312: Update the response parsing flow around
runsPageSchema.safeParse so response.json() rejection is caught and converted
into the same ApiError(502) contract used for schema-invalid responses. Preserve
the existing validation error handling for parsed responses and include clear
upstream-response context in the normalized error.
In `@packages/cloud/src/create-project.ts`:
- Around line 116-123: Wrap the JSON parsing in the response-handling flow
around schema.safeParse so malformed response text is caught and converted into
an ApiError with status 502. Preserve the existing validation error handling and
fallback context for successfully parsed but schema-invalid responses.
In `@packages/local-runner/src/cloud-common.ts`:
- Around line 92-98: Remove the catch-and-return-empty behavior from
extractOutputs so parseSnapshot errors propagate for malformed snapshots.
Preserve normal output extraction for valid snapshots, allowing getCloudRun to
handle parsing failures instead of reporting success with empty outputs.
In `@packages/local-runner/src/cloud-runs.ts`:
- Around line 104-110: Update the notebook lookup flow around findNotebook so it
distinguishes a genuine no-match from lookup failures: return { runs: [] } only
when no notebook is found, while allowing authentication, network, and server
errors to propagate instead of converting them to undefined. Remove or narrow
the catch attached to findNotebook without changing the existing successful
lookup behavior.
In `@packages/local-runner/src/run-in-cloud.ts`:
- Around line 69-71: Update the documentation for the cloud-run function near
openInCloud to state that operational failures during triggering, lookup,
creation, polling, snapshot fetching, or parsing may throw, rather than claiming
only missing configuration throws. Preserve the existing success:false + error
behavior where applicable.
- Around line 116-124: Update createFromFile to return an old-to-new block ID
mapping alongside notebookId and projectId, then use that mapping in the run
flow before calling triggerNotebookRun. Remap options.blockIds to the newly
created notebook’s IDs, preserving the existing selection behavior and applying
the same fix to the corresponding flow around the later triggerNotebookRun call.
In `@packages/local-runner/src/serve-static.test.ts`:
- Around line 133-140: Update the cloud-runs route handler around
decodeURIComponent(runMatch[1]) so malformed encoded run IDs are caught by the
route’s existing error handling and return HTTP 400 instead of 500. Add a test
in serve-static.test.ts covering an invalid percent-encoded run ID such as
%E0%A4%A and asserting the 400 response.
---
Outside diff comments:
In `@packages/local-runner/src/run-in-cloud.ts`:
- Around line 82-88: Separate the Cloud notebook ID from the local source
notebook ID in the run flow. Use a locally resolved notebook ID from
resolveNotebookId(file) when calling coerceInputs and when creating or locating
the source notebook, while preserving options.notebookId for Cloud API
operations that require the assigned ID.
- Around line 99-116: Update the findNotebook call in the run-in-cloud flow to
stop swallowing lookup errors; remove the blanket catch so authentication,
network, and server failures propagate. Keep the existing creation branch gated
only on a successful lookup returning no result.
🪄 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: 80b2ad9f-23d5-40a1-adc3-7de094dddc73
📒 Files selected for processing (15)
examples/local-runner/run-app/README.mdexamples/local-runner/run-app/index.htmlpackages/cloud/README.mdpackages/cloud/src/cloud-runs.tspackages/cloud/src/create-project.test.tspackages/cloud/src/create-project.tspackages/cloud/src/index.tspackages/local-runner/src/cloud-common.tspackages/local-runner/src/cloud-runs.test.tspackages/local-runner/src/cloud-runs.tspackages/local-runner/src/index.tspackages/local-runner/src/run-in-cloud.test.tspackages/local-runner/src/run-in-cloud.tspackages/local-runner/src/serve-static.test.tspackages/local-runner/src/serve-static.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- examples/local-runner/run-app/README.md
- packages/local-runner/src/index.ts
- packages/cloud/src/index.ts
- packages/local-runner/src/serve-static.ts
…n errors
Second review pass. The theme is the same each time: a value that means two
things, or an error quietly turned into a fact.
options.notebookId addresses a notebook in Deepnote, but it was also used to
scope input coercion against the *local* file. Those are only the same id by
luck. notebooksInScope silently widens to every notebook when an id matches
none — harmless for one notebook, wrong for several, where a name defined
twice gets typed against whichever came first. A cloud id names no local
notebook as a matter of course, so that fallback was doing the deciding. The
two are now resolved separately, and an id that names none of several notebooks
is an error rather than a guess — unless there are no inputs, when it cannot
matter.
blockIds were still forwarded on the find-by-name path. The create path remaps
them; this one has no mapping to offer, because the notebook was matched by
name and Deepnote gave its blocks their own ids. It now refuses instead of
running something else.
listCloudRuns turned any lookup failure into "no runs". "Found nothing" and
"couldn't look" are different answers and { runs: [] } can only say the first.
The demo route keeps its own catch, so it stays quiet.
Malformed upstream responses bypassed the error contract three ways: JSON.parse
and response.json() threw raw SyntaxErrors past ApiError, and a snapshot that
would not parse became a successful run with no outputs — a claim about the
notebook, and a false one.
GET /api/cloud-runs/{runId} decoded its id outside the try, so bad percent-
encoding was a 500. It is a malformed request: 400.
The slider docs were wrong, twice, so this time I ran it: the snapshot stores
"6" because the schema says a slider's value is a string, but the block's
generated Python is `months = 6`, so the kernel has an int. The showcase's
"input values arrive as strings" comment said otherwise and is what I read it
from; both now say what the kernel actually gets.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The helper it replaces was called rejectUnaddressableBlockIds, and cspell was right that "Unaddressable" isn't a word — the pre-push spell-check caught it. Rather than teach the dictionary a coinage, the guard now sits inline where the by-name branch is, which is where a reader meets the constraint anyway. No behavior change; folds into the previous commit if you squash on merge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Third review pass, and the same two shapes again. The ambiguity fix last round only covered coercion. But `notebookNameFor` has a silent fallback of its own — to the first notebook — and both the by-name lookup and the create path go through it. So an explicit cloud id against a multi-notebook file with no inputs sailed past the guard and could find, or create, the wrong notebook entirely. Which notebook *of the file* a request means is now resolved in one place, and refused when unanswerable. Still only asked where it matters: a run that neither coerces nor falls back never needs to know, so passing a cloud id to a multi-notebook file keeps working. createFromFile was being handed the cloud id to pick a local notebook by, which is the same mistake one level down. The run-app could paint an old run over a new one. The history load on open, a run, and a click on a past run all render, and all finish whenever they like: click Run quickly enough after opening and the auto-loaded history would land on top of your fresh output. Each now takes a generation before it starts and drops its result if something newer has claimed the canvas. That counter also replaces `rendered` — "has anything claimed it" was the question all along. Docs: the package README still said coerced slider strings are what the kernel sees (it is `months = 7`, an int — the string is storage). runInCloud's docstring still promised only missing config throws, which stopped being true when lookup and parse failures started throwing rather than being swallowed; the line is now about whether Deepnote ran the notebook and told us, not whether the news is good. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A .deepnote file may hold two notebooks with the same name — nothing forbids it — and createFromFile matched the created notebook back by that name. Ask for the second and you got the first: the run, and the block-id map with it, silently pointed at the wrong notebook. createProject creates notebooks in the order it was handed them, so position identifies them and a name cannot. The same ordering already maps the blocks; this just stops the notebook itself being the odd one out. The regression fails on the old logic — it picks the first notebook, whose block map has no id for the block being asked for, so the targeted run errors out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…that it exists The persistence test checked snapshotPath was defined, the file existed, and something in snapshots/ ended in .snapshot.deepnote — all of which a truncated or mis-serialized write would also satisfy. runWithInputs builds the returned snapshot and the persisted one from the same (file, outputs, timing) specifically so they cannot drift. That guarantee was worth nothing while nothing checked it, so the test now reads the file back and compares. Confirmed it catches drift by introducing some. Raised by CodeRabbit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@coderabbitai — your inline comments failed to post ("GitHub's internal server error"), so I worked from the review bodies. Verified each against current code; two were already fixed, one is done, two I'm pushing back on. Done — Assert persisted snapshot contents, not only file existence. Fair, and the existence checks would have passed on a truncated or mis-serialized write. Already fixed before this review landed Reject malformed encoded run IDs with 400 — Add regression coverage for rejected-request branches — all four already exist in Pushing back Use Reuse the shared input-coercion helper. The shared logic already is shared — |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- candidate files ---'
fd -a -t f '^(AGENTS\.md|run-with-inputs\.test\.ts|serve-static\.ts|serve-static\.test\.ts|apply-input-overrides\.ts|coerce-input-value\.ts|run-in-cloud\.ts)$' . | sort
printf '%s\n' '--- source outlines ---'
for f in \
packages/local-runner/src/run-with-inputs.test.ts \
packages/local-runner/src/serve-static.ts \
packages/local-runner/src/serve-static.test.ts \
packages/local-runner/src/apply-input-overrides.ts \
packages/local-runner/src/coerce-input-value.ts \
packages/local-runner/src/run-in-cloud.ts
do
if [ -f "$f" ]; then
echo "### $f"
ast-grep outline "$f" --items all || true
fi
done
printf '%s\n' '--- relevant lines ---'
rg -n -C 4 \
'snapshotYaml|readFile|decodeURIComponent|Invalid JSON|MAX_BODY_BYTES|symlink|inputBlocksByName|coerceInputValueForBlocks|notebooksInScope|coerceInputs|dedent' \
AGENTS.md packages/local-runner/src \
-g '*.ts' -g 'AGENTS.md'Length of output: 59043
Withdrawing the two remaining nits. ✏️ Learnings added
|
…notebook The docstring still described the behaviour the duplicate-name fix removed — it was documenting the bug. The notebook is identified by position now, and a mismatch throws rather than quietly running the first one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A real run failed and neither the user nor I could say why. It took reading the raw API to find the reason, which was sitting in the snapshot we had already decided not to fetch. A failed run was a dead end by construction, at the one moment information is worth something: - The snapshot was fetched only on success. `const snapshotYaml = success ? ... : null`, with a comment claiming "a failed run has no snapshot to wait for". That failed run had an 80KB one, holding both the outputs of the five blocks that ran fine and the only account of what broke. - `error` came back undefined. describeRunError reads `run.error`, which the API sets to null on a genuine failure more often than not, so callers got `success: false` with nothing attached. - viewUrl was built only on success — so the one place that could have shown the answer was the one link we withheld. Now: fetch the snapshot whatever the status, keep the outputs it holds, always build viewUrl, and describe the failure from the snapshot when the API won't — a block's error output, or an agent block's `deepnote_agent_status: failed`, which is the only place a failed agent is recorded at all. The bare status is the last resort; never silence. This also fixes the snapshot race: the settle loop retries a terminal run whose snapshot has not landed, rather than re-fetching once and reporting a successful run with no outputs. First retry is immediate — usually enough — and only then does it wait. Both entry points were wrong the same way, and I fixed runInCloud first and getCloudRun only after the live check caught it still failing — which is the same fix-the-instance-not-the-shape mistake this PR keeps making. The diagnosis now lives in cloud-common, where both read it. Verified against the run that actually failed: it now reports "The agent block failed (deepnote_agent_status: failed)" and keeps 6 blocks of output, where before it said nothing and returned none. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The settling loop caught every fetchSnapshotContent failure and returned null, which flattened two different nothings into one. fetchSnapshotContent returns null when the run has no snapshot yet — worth retrying, and reportable as no outputs — but throws when there is a snapshot it could not read. Swallowing the second meant a failed download surfaced as `success: true` with no outputs, or as a failed run explained away with "Deepnote reported no reason". Both invent an answer out of an error. Worse, getCloudRun let that same failure throw, so the fresh-run path and the history path disagreed about the same run — which is the bug this whole thread started with, in miniature. Now the loop retries either kind (a download can fail transiently too), and only decides at the end: a snapshot that never attached returns null, one that never became readable throws the error it actually hit. Also stop the re-fetch test waiting on real 1.5s sleeps — its getRun rejects every time, so it walked the whole backoff for nothing. The file drops from 3.4s to 0.3s. Raised in review at d440a45. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Caution Review failedAn error occurred during the review process. Please try again later. Comment |
dinohamzic
left a comment
There was a problem hiding this comment.
@jamesbhobbs I ran a Sol review specifically to surface inconsistency with deepnote-internal, multiple issues came up. Can you please check. 🙏
I compared this branch against the current Cloud implementation in ../deepnote-internal and found the following blocking issues.
[P1] Page through project lookup
packages/cloud/src/projects.ts:49
GET /v2/projects defaults to 50 items and returns a required nextPageToken. This performs one unfiltered request and ignores pagination, so older matching projects appear absent and createIfMissing creates duplicates. An invalid response shape is also converted to absence. Please filter with nameContains, exhaust matching pages, and throw on malformed responses.
[P1] Do not replace a requested notebook
packages/cloud/src/projects.ts:69
Cloud notebook names are unique within a project, so when notebookName is provided, another notebook is never an equivalent fallback. In a newer partial project this can run its first notebook instead of finding the requested notebook in an older matching project. Only use the first notebook when no notebookName was requested.
[P1] Reuse the seeded Notebook 1
packages/cloud/src/create-project.ts:154
POST /v2/projects always seeds a notebook named Notebook 1, while POST /v2/notebooks returns 409 for duplicate names. Because source notebooks are created before the seed is deleted, a common source named Notebook 1 fails immediately and leaves a partial project. Duplicate source names fail later for the same reason. Please reuse the seeded notebook when applicable and validate source-name uniqueness before creating anything.
[P1] Translate SQL integration metadata
packages/local-runner/src/run-in-cloud.ts:292
In a .deepnote file, an SQL integration is stored in metadata.sql_integration_id. Cloud explicitly rejects that metadata key and requires top-level integrationId. Parsed Deepnote blocks do not have the top-level property checked here, so every integrated SQL block fails after the project and notebook have already been created. Extract and remove the metadata key before posting the block.
[P1] Remap notebook-function targets
packages/local-runner/src/run-in-cloud.ts:295
Cloud assigns every created notebook a new ID, but function_notebook_id is forwarded unchanged. The Cloud executor resolves exactly that ID, so a recreated block either invokes the original module or fails because the target is unavailable. Create all notebooks first, build an old-to-new notebook ID map, and rewrite references to notebooks included in the file before creating blocks.
[P1] Match only notebook-not-found errors
packages/local-runner/src/run-in-cloud.ts:392
The Cloud run API returns a 400 response containing “Block not found in notebook” when a selected block is invalid. The broad /not found/i check treats that as a missing notebook and starts name/create recovery for a notebook that exists, potentially creating an unrelated duplicate project. Match Cloud’s specific “Notebook not found” response instead of every not-found message.
[P2] Validate block IDs before creating
packages/local-runner/src/run-in-cloud.ts:162
When the initial notebook is genuinely absent, createFromFile persists the project before mapBlockIds verifies that every requested source block belongs to the target notebook. A typo therefore leaves a project behind and then throws. Validate membership, non-emptiness, and uniqueness against the local target before calling createProject.
[P2] Reject undefined Cloud inputs
packages/local-runner/src/run-in-cloud.ts:401
Cloud accepts only input names defined by input blocks, with values limited to string, boolean, or string[]. It has no generic kernel-injection behavior. Passing an unknown name through causes an existing run to return 400; on the create path it fails only after the project was persisted. Reject unknown names before network activity and tighten TriggerRunBody to the Cloud value union.
[P2] Require a notebook for run history
packages/local-runner/src/cloud-runs.ts:111
Cloud run history is scoped to one notebook ID, but this silently selects the first local notebook whenever the file contains several. That can return valid-looking history for the wrong notebook and is inconsistent with runInCloud, which rejects this ambiguity. Add a local notebook selector or require an explicit ID for multi-notebook files.
[P2] Preserve the Cloud completion timestamp
packages/cloud/src/cloud-runs.ts:61
GET /v2/runs/{id} returns completedAt, not finishedAt. Because the permissive schema ignores completedAt and normalization reads only raw.finishedAt, current Cloud responses lose their completion timestamp. Parse completedAt and either expose it directly or normalize it intentionally.
[P2] Restore the package PNG exception
.gitignore:1
Git uses the last matching ignore rule, so placing !packages/**/*.png before *.png leaves package PNG assets ignored. The file also duplicates nearly every entry. Remove the duplicates and place the exception after the wildcard.
| for (const project of projects) { | ||
| const notebooks = project.notebooks ?? [] | ||
| const match = query.notebookName ? notebooks.find(notebook => notebook.name === query.notebookName) : undefined | ||
| const notebook = match ?? notebooks[0] |
There was a problem hiding this comment.
When no match is found, here we default to the first notebook instead of erroring out. This can be very confusing from a user POV. Imo we should default to the first notebook only if no name is provided, not as a fallback when a name is provided but it's not found.
What
A new workspace package,
@deepnote/local-runner, that extracts a reusable mechanism: run a.deepnotenotebook with edited inputs and get outputs + a snapshot back — the building block a static page needs to drive execution. It runs a notebook two ways behind one small API: a local Python kernel, or Deepnote Cloud. Two example apps live underexamples/local-runner/.API
runWithInputs(input, inputs, options?)— run locally.inputis a path, raw.deepnoteYAML, or aDeepnoteFile. Reuses@deepnote/blocks(parse +coerceInputVariableValue),@deepnote/runtime-core(ExecutionEngine), and@deepnote/convert(snapshots) in-process — no shelling out. Returns per-block outputs in execution order, anExecutionSummary, and a snapshot that is persisted next to the file by default (persistSnapshot: falseto skip), likedeepnote run.runInCloud(input, inputs, options?)— run the same notebook in Deepnote Cloud via@deepnote/cloud. Resolves the notebook id from the file, falls back to finding it by project/notebook name, and if it isn't in the cloud yet creates it there — project, notebook, blocks — and runs it in the same call, reportingcreated: true. One click, no browser step: a token is required either way, so there is nothing a logged-in session could add. PasscreateIfMissing: falseto fail instead. On success it returns the parsed outputs plus aviewUrldeep-link to the notebook's runs sidebar. Inputs are coerced to the shape the API expects before sending.serveStatic({ dir, notebookPath, cloudToken? })— a deliberately smallnode:httphelper exposingGET /api/info,POST /api/run(local),POST /api/run-cloud(cloud), and static files with a path-traversal guard. Binds to127.0.0.1. No WebSocket/watch/theme/rendering.listCloudRuns(input, options?)/getCloudRun(runId, options?)— a notebook's run history in Deepnote, and any past run's outputs read back from its snapshot without re-running it. They live incloud-runs.ts; nothing there executes anything.openInCloud,applyInputOverrides,listInputBlocks,loadDeepnoteFile.New
@deepnote/cloudhelpersThis PR adds to the package #417 introduced (it doesn't duplicate the runs client):
create-project.ts—createProject(baseUrl, token, spec)builds a project, its notebooks and their blocks over the authenticated API (POST /v2/projects,/v2/notebooks,/v2/blocks) and returns the ids Deepnote assigned. It takes a plainProjectSpecrather than aDeepnoteFile, so the client stays thin anddeepnote run --cloud --pushcan build on it rather than reinvent it.cloud-runs.ts— also gainslistNotebookRuns(GET /v2/notebooks/{id}/runs).import.ts—uploadNotebook(bytes, fileName, { domain })→{ importId, launchUrl }: the unauthenticatedPOST /v1/import/init+ presigned PUT. Untouched, and still the right flow fordeepnote open, which has no token and genuinely wants a browser. It was only ever wrong forrunInCloud, which always has one.projects.ts—findNotebook(locate a notebook by project/notebook name viaGET /v2/projects),getWorkspace(GET /v2/me), andnotebookUrl(build the runs deep-link).Design notes
(file, outputs, timing).summary.failedBlocks; only infra/config errors throw (no Python env, missing toolkit, invalid file, missing cloud token, orpersistSnapshotwithout a path).deepnote serveCLI command are intentionally out of scope (future work).Demo
examples/local-runner/— two small reference apps sharing a visual language. run-app builds a control per input and runs the notebook live (a local Python kernel, or Run in cloud viarunInCloud), rendering the returned outputs, with a Cloud runs sidebar listing the notebook's run history in Deepnote — click any run to render its snapshot without re-running it. snapshot-viewer is a fully static page that renders an already-run snapshot — outputs, a chart, and a precomputed agent readout — with no server or kernel. Each is a one-commandpnpm example:*away.Tests
Unit tests mock
ExecutionEngineand@deepnote/cloud(no Python, no network): input coercion/apply, therunWithInputsinvariant + snapshot validity + failure passthrough, the fullrunInCloudflow (by-id, find-by-name, create-if-missing, block-id remapping, refusing to guess which notebook a cloud id means, lookup failures not being read as absence, viewUrl, token-required),createProject(ordering, placeholder cleanup, progress, auth, non-JSON bodies), andlistCloudRuns/getCloudRun, and theserveStaticroutes incl./api/run-cloud, bad-body error JSON, content-type, and an encoded path-traversal rejection. A real end-to-end local test is gated behindDEEPNOTE_TOOLKIT_PYTHON; the cloud path was verified live against the API.@deepnote/local-runner+@deepnote/cloud+@deepnote/cli+@deepnote/blockssuites green (1 skipped: the gated integration test); typecheck + biome clean.🤖 Generated with Claude Code
Summary by CodeRabbit
@deepnote/local-runnerwith browser-safe snapshot parsing/viewing, input block discovery, and schema-aware input overrides with optional notebook scoping.serveStaticfor local/cloud notebook execution plus secure static hosting.