feat(cli): schedule notebooks in Deepnote Cloud - #437
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 ignored due to path filters (1)
📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds recurring Deepnote Cloud scheduling across Cloud APIs, local-runner orchestration, the CLI, static-server endpoints, and example UI flows. The implementation validates schedule expressions, resolves or creates notebooks, upserts schedules, supports text and JSON output, and coordinates concurrent cloud operations. Tests, exports, completions, examples, dependencies, and documentation are updated. Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant CLI
participant scheduleInCloud
participant DeepnoteCloud
CLI->>scheduleInCloud: local file, cron, timezone
scheduleInCloud->>DeepnoteCloud: resolve or create notebook
scheduleInCloud->>DeepnoteCloud: upsert recurring schedule
DeepnoteCloud-->>scheduleInCloud: schedule result
scheduleInCloud-->>CLI: formatted result
Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (4 passed)
Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #437 +/- ##
==========================================
+ Coverage 87.36% 87.78% +0.42%
==========================================
Files 181 187 +6
Lines 9494 9927 +433
Branches 2624 2768 +144
==========================================
+ Hits 8294 8714 +420
- Misses 1199 1212 +13
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: 3
🧹 Nitpick comments (2)
packages/cli/src/commands/schedule.ts (1)
70-77: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNo progress feedback wired for project creation during scheduling.
onWarningis forwarded toscheduleInCloud, butonCreateProgressisn't, so creating a brand-new cloud project as part ofschedulegives no feedback while it runs (per the local-runner contract,createFromFileacceptsonCreateProgress).🤖 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/schedule.ts` around lines 70 - 77, Update the schedule command’s scheduleInCloud call to pass an onCreateProgress callback alongside onWarning, reusing the local-runner progress behavior so project creation reports feedback during scheduling. Preserve the existing scheduling options and warning handling.packages/cli/src/commands/schedule.test.ts (1)
123-127: 📐 Maintainability & Code Quality | 🔵 TrivialConsider covering the browser-open failure and negative-open paths.
No test exercises
openInBrowserrejecting (see the propagation issue flagged inschedule.tslines 79-81) or confirmsopenInBrowseris skipped when--openisn't passed. Worth adding once that fix lands.🤖 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/schedule.test.ts` around lines 123 - 127, Extend the schedule command tests around createScheduleAction to cover both open paths: verify openInBrowser is not called when options({ open: false }) is used, and mock it to reject when options({ open: true }) is used, asserting the command propagates the failure. Keep the existing successful browser-open assertion unchanged.
🤖 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/schedule.ts`:
- Around line 79-81: Guard the best-effort openInBrowser call in
createScheduleAction so failures are swallowed after scheduleInCloud succeeds.
Apply the same non-failing promise handling pattern used by the local-runner
side effect, while preserving the existing options.open and result.viewUrl
conditions and successful command/JSON output.
In `@packages/local-runner/src/schedule-in-cloud.ts`:
- Around line 74-98: Update the lookup and creation flow around findNotebook and
createFromFile so a missing notebook in an existing same-named project does not
create a duplicate project. Resolve the project independently using the exact
project name, then add the notebook to that project; if the project cannot be
resolved, fail with a clear error. Preserve the existing found-notebook and
createIfMissing behavior.
- Line 58: Update the request body construction in the schedule-in-cloud flow so
an explicitly supplied timezone, including an empty string, is preserved instead
of omitted; distinguish absence from falsy values when checking
options.timezone, allowing upsertNotebookSchedule to perform its existing
validation.
---
Nitpick comments:
In `@packages/cli/src/commands/schedule.test.ts`:
- Around line 123-127: Extend the schedule command tests around
createScheduleAction to cover both open paths: verify openInBrowser is not
called when options({ open: false }) is used, and mock it to reject when
options({ open: true }) is used, asserting the command propagates the failure.
Keep the existing successful browser-open assertion unchanged.
In `@packages/cli/src/commands/schedule.ts`:
- Around line 70-77: Update the schedule command’s scheduleInCloud call to pass
an onCreateProgress callback alongside onWarning, reusing the local-runner
progress behavior so project creation reports feedback during scheduling.
Preserve the existing scheduling options and warning handling.
🪄 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: 2eb48917-4ad0-4e24-86b3-08c98f91e719
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (19)
packages/cli/README.mdpackages/cli/package.jsonpackages/cli/src/cli.test.tspackages/cli/src/cli.tspackages/cli/src/commands/schedule.test.tspackages/cli/src/commands/schedule.tspackages/cli/src/completions.tspackages/cli/src/utils/schedule-expression.test.tspackages/cli/src/utils/schedule-expression.tspackages/cloud/src/index.tspackages/cloud/src/schedules.test.tspackages/cloud/src/schedules.tspackages/local-runner/README.mdpackages/local-runner/src/index.tspackages/local-runner/src/run-in-cloud.tspackages/local-runner/src/schedule-in-cloud.test.tspackages/local-runner/src/schedule-in-cloud.tsskills/deepnote/SKILL.mdskills/deepnote/references/cli-schedule.md
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/local-runner/src/serve-static.ts`:
- Around line 172-193: Protect the POST /api/schedule-cloud handler by
validating any supplied Origin against this server’s own origin before reading
or processing the scheduling request; reject mismatches with HTTP 403 while
allowing same-origin and absent-Origin requests. Add a test covering a foreign
Origin and asserting the 403 response, reusing the server’s existing
origin-validation utilities or request-handling symbols.
🪄 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: cac69ecc-d68d-40e7-b7bb-3f120cb4b7c6
📒 Files selected for processing (14)
examples/local-runner-showcase.deepnoteexamples/local-runner/README.mdexamples/local-runner/run-app/README.mdexamples/local-runner/run-app/index.htmlexamples/local-runner/run-app/serve.mjsexamples/local-runner/schedule-cloud.mjsexamples/scheduled-cloud-run.deepnotepackage.jsonpackages/local-runner/README.mdpackages/local-runner/src/index.tspackages/local-runner/src/schedule-in-cloud.test.tspackages/local-runner/src/schedule-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 (1)
- packages/local-runner/README.md
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/local-runner/src/schedule-in-cloud.ts (1)
77-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the shared "find-or-create cloud notebook" logic into one helper.
Both files implement the same sequence — coordinate via
coordinateCloudNotebook,findNotebookby name, thenfindProject+createFromFile(destination)if missing — differing only in the inputs payload and options passed through. Based on learnings, this exact logic already needed the same duplicate-project fix applied twice (once per file); centralizing it avoids that recurrence.
packages/local-runner/src/schedule-in-cloud.ts#L77-L115: replace this block with a call to a sharedresolveOrCreateCloudNotebook(...)helper, passinginputs: {}and the schedule's progress/warning callbacks.packages/local-runner/src/run-in-cloud.ts#L142-L180: replace this block with the same shared helper, passing the run'sinputsand fulloptions.♻️ Suggested shared helper sketch
export async function resolveOrCreateCloudNotebook( baseUrl: string, token: string, file: DeepnoteFile, localId: string, notebookName: string | undefined, allowCreate: boolean, notFoundError: unknown, createOptions: { inputs: Record<string, unknown> } & Pick<RunInCloudOptions, 'onCreateProgress' | 'onWarning'> ): Promise<CloudNotebookResolution> { return coordinateCloudNotebook( { baseUrl, token, projectName: file.project.name, notebookId: localId, notebookName, allowCreate }, async () => { const found = await findNotebook(baseUrl, token, { projectName: file.project.name, notebookName }) if (found) return { notebookId: found.notebookId, projectId: found.projectId, created: false } if (!allowCreate) throw notFoundError const project = await findProject(baseUrl, token, file.project.name) const createdTarget = await createFromFile( baseUrl, token, file, { notebookId: localId, inputs: createOptions.inputs }, createOptions, project ) return { ...createdTarget, created: true } } ) }🤖 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/schedule-in-cloud.ts` around lines 77 - 115, Extract the duplicated find-or-create flow into a shared resolveOrCreateCloudNotebook helper. In packages/local-runner/src/schedule-in-cloud.ts lines 77-115, replace the inline coordination with this helper using inputs: {} and the schedule progress/warning callbacks; in packages/local-runner/src/run-in-cloud.ts lines 142-180, replace its equivalent block using the run inputs and full creation options. Preserve coordination, notebook lookup, allowCreate/not-found handling, project lookup, and createFromFile behavior, and update both callers to use the shared helper.Source: Learnings
🤖 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/schedule-in-cloud.ts`:
- Around line 77-115: Extract the duplicated find-or-create flow into a shared
resolveOrCreateCloudNotebook helper. In
packages/local-runner/src/schedule-in-cloud.ts lines 77-115, replace the inline
coordination with this helper using inputs: {} and the schedule progress/warning
callbacks; in packages/local-runner/src/run-in-cloud.ts lines 142-180, replace
its equivalent block using the run inputs and full creation options. Preserve
coordination, notebook lookup, allowCreate/not-found handling, project lookup,
and createFromFile behavior, and update both callers to use the shared helper.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: bd2c1304-87ec-4bd1-b575-be44d44734b3
📒 Files selected for processing (9)
examples/local-runner/run-app/README.mdexamples/local-runner/run-app/index.htmlpackages/local-runner/README.mdpackages/local-runner/src/cloud-notebook-coordinator.test.tspackages/local-runner/src/cloud-notebook-coordinator.tspackages/local-runner/src/run-in-cloud.tspackages/local-runner/src/schedule-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 (2)
- examples/local-runner/run-app/README.md
- packages/local-runner/README.md
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/recurring-schedule.test.ts`:
- Around line 13-26: Expand the parameterized tests around
resolveRecurringSchedule to cover array-form schedules, valid time boundaries
00:00 and 23:59, weekday values 0 and 6, month-day values 1 and 31, plus
malformed inputs containing special characters or whitespace. Add assertions for
the exact resolved schedule output on valid boundary cases while preserving the
existing RecurringScheduleError checks for invalid inputs.
In `@packages/local-runner/src/recurring-schedule.ts`:
- Around line 43-50: Update the monthly validation in the recurring schedule
handling around the frequency check to reject dayOfMonth values 29–31, since the
current cron expression skips shorter months. Extend the existing
RecurringScheduleError validation while preserving valid values 1–28 and the
current cron and description 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: e5c28141-0db0-4c5c-beda-013cc68f5266
📒 Files selected for processing (8)
examples/local-runner/run-app/README.mdexamples/local-runner/run-app/index.htmlpackages/local-runner/README.mdpackages/local-runner/src/index.tspackages/local-runner/src/recurring-schedule.test.tspackages/local-runner/src/recurring-schedule.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 (2)
- examples/local-runner/run-app/README.md
- packages/local-runner/README.md
dinohamzic
left a comment
There was a problem hiding this comment.
AI review:
[P1] Preserve init-notebook semantics
packages/local-runner/src/schedule-in-cloud.ts:102
The create-if-missing path creates every notebook but never preserves file.project.initNotebookId. Deepnote’s public creation API leaves init_notebook_id null, while scheduled execution prepends init only when that field is set.
An init-backed file therefore schedules successfully but can fail on a cold run without its setup notebook. Preserve the designation through an import/API path, or reject this case before creating a broken schedule.
[P1] Share only neutral creation state
packages/local-runner/src/cloud-notebook-coordinator.ts:38
The coordinator coalesces the first caller’s entire resolver while its key excludes caller-specific inputs and block IDs.
If a run wins the race, createFromFile persists its one-off input overrides and the concurrent schedule inherits those values as recurring defaults. If scheduling wins, the file defaults are persisted. Invalid run-only block IDs can likewise reject an otherwise-valid concurrent schedule.
Prevalidate run-only options and coordinate a neutral, source-default creation operation.
[P1] Do not trust Host for same-origin validation
packages/local-runner/src/serve-static.ts:262
Host is client-controlled, making this check vulnerable to DNS rebinding. Once an attacker hostname resolves to 127.0.0.1, the browser sends matching attacker-controlled Origin and Host values, allowing the token-backed scheduling mutation.
Validate the parsed origin against the actual loopback listener address and port, or require an unguessable CSRF token.
[P2] Reject ambiguous sibling mappings
packages/local-runner/src/run-in-cloud.ts:400
Each local sibling is mapped to the first cloud notebook with the same name. The .deepnote schema permits duplicate notebook names, so two local helpers named Helper both map to one cloud ID. A notebook-function targeting the second helper can then silently execute whichever cloud helper exists.
Detect ambiguous referenced names and fail closed, as the fresh-project path already does for duplicate names.
[P2] Select a project that accepts additional notebooks
packages/cloud/src/projects.ts:142
The list API returns projectType, but the parser discards it and findProject blindly selects the newest exact-name project.
Deepnote-internal rejects notebook creation in single-notebook and Agent projects. One of those projects can therefore shadow an older usable standard project and make create-if-missing fail with HTTP 409.
Preserve projectType and select a standard project as the destination.
[P2] Resolve composed init projects consistently
packages/cli/src/commands/schedule.ts:104
Without --notebook, the CLI rejects every two-notebook file as ambiguous, including the supported [init, main] shape with exactly one runnable main notebook.
resolveSnapshotNotebookId already resolves this shape consistently elsewhere. Pass the project/init metadata through that helper and add an init-backed CLI test.
[P2] Classify malformed files as usage errors
packages/cli/src/commands/schedule.ts:40
deserializeDeepnoteFile throws ParseError subclasses for malformed YAML or schema, but this classification omits them and exits with code 1.
The new schedule documentation promises exit code 2 for invalid files, and neighboring CLI commands already treat ParseError as invalid usage. Add it here and test a malformed file.
[P2] Align first-schedule safety defaults
packages/cli/src/commands/schedule.ts:72
The internal API accepts only cron and timezone. Its create branch supplies neither republish nor autoPauseOnFailure, so database defaults leave both disabled.
The product UI defaults both settings to enabled. Consequently, CLI-created schedules do not refresh an existing published app and continue running after repeated failures.
Align the internal API’s creation defaults or expose these settings in the request contract before shipping the CLI.
[P2] Avoid concentrating hourly jobs at minute zero
packages/cli/src/utils/schedule-expression.ts:76
--hourly always emits minute zero and cannot accept --at. Deepnote-internal deliberately defaults scheduling to the current minute to avoid full-hour execution spikes.
Preserve or distribute the creation minute, or provide an hourly-minute option, rather than aligning every CLI-created hourly schedule.
Cross-reference note
Monthly days 1–31 match Deepnote’s internal scheduling UI. CodeRabbit’s suggested 1–28 restriction should not be adopted.
The same-origin check compared Origin to the Host header. Both are client controlled, so matching them only proves the caller sent itself a matching pair — which is what a DNS-rebinding page does once its hostname resolves to 127.0.0.1, carrying this server's cloud token off the back of it. Check the Origin against the socket the request arrived on instead: a loopback hostname, and the port from req.socket.localPort rather than a claimed one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A run and a schedule racing for the same missing notebook resolve through one create. That create baked in the first caller's input overrides, so a schedule that joined a run inherited that run's one-off arguments as its recurring defaults, and a run-only blockIds typo failed whichever schedule joined it. Create the file as it stands. A run still executes with its overrides — it passes them to triggerNotebookRun, where they belong to the run rather than to the notebook. Validate blockIds before entering the coordinator, so a bad argument fails only the call that made it. Also refuse an ambiguous sibling mapping: the schema permits duplicate notebook names, and matching by name pointed a notebook-function call at whichever cloud notebook came first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…it notebook A scheduled run prepends the project's init notebook only when Deepnote has init_notebook_id set, and the public creation API has no field for it. An init-backed file therefore scheduled successfully and then ran without its setup at whatever hour the cron named — the least visible place to find out. Refuse on the create path with a way through: import the project once, which keeps the designation, then schedule it. A project already in Deepnote is unaffected. Also cover the recurring-schedule parser's boundaries: array input, 00:00 and 23:59, weekday 0 and 6, day 1 and 31, and malformed times carrying cron metacharacters, whitespace, or a trailing expression. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… files
Three fixes to the schedule command:
--hourly always emitted minute zero and refused --at, so every CLI-created
hourly schedule landed on the same execution spike. Use the creation minute by
default, and let --at pin one (":15", "15", or an HH:mm whose hour is 00).
Without --notebook, every two-notebook file was rejected as ambiguous — including
the composed [init, main] shape, which has exactly one runnable notebook and is
resolved consistently everywhere else. Route it through
resolveSnapshotNotebookId, and keep the refusal for files it cannot resolve.
A malformed or schema-invalid file exited 1, while the command's own help text
promises 2 for a bad file. Classify ParseError as invalid usage.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@dinohamzic thanks — worked through all nine. Seven are fixed in Fixed[P1] Preserve init-notebook semantics — [P1] Share only neutral creation state — took the neutral-create route. Two existing tests asserted the old baked-in behaviour and were inverted deliberately — worth a look, since that was a documented design choice I have changed: [P1] Do not trust [P2] Reject ambiguous sibling mappings — fails closed now. If a notebook-function block names a local sibling whose name is shared with another notebook in the file, it throws rather than mapping both onto the first cloud match. [P2] Resolve composed init projects consistently — routed through [P2] Classify malformed files as usage errors — [P2] Avoid concentrating hourly jobs at minute zero — Needs your input[P2] Select a project that accepts additional notebooks — I can preserve [P2] Align first-schedule safety defaults — agreed on the substance, but both halves land outside this PR: CI is green and the full suite passes (2764 tests). Leaving it in draft. |
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 (4)
packages/cli/src/utils/schedule-expression.ts (1)
71-78: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy liftValidate cron field syntax before scheduling.
Line 76 only checks the field count. A value such as
invalid foo bar baz quxpasses and reachesscheduleInCloud.Validate each field with the same cron grammar that Deepnote Cloud accepts. Add invalid-token tests.
🤖 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/utils/schedule-expression.ts` around lines 71 - 78, Update the cron validation in the options.cron handling path to validate each of the five fields against the cron grammar accepted by Deepnote Cloud, not only the field count. Reject invalid tokens before scheduleInCloud is reached, preserve the existing empty and incorrect-count errors, and add tests covering malformed cron fields such as “invalid foo bar baz qux”.packages/local-runner/src/serve-static.ts (1)
175-176: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winApply the origin guard to the other token-backed route.
rejectCrossOriginRequestprotects/api/schedule-cloudonly.POST /api/run-cloudat Line 153 also usesoptions.cloudTokenand mutates Cloud state. A page on another origin can still reach it over loopback, with the same DNS-rebinding path this guard was added to close.Call the guard for
/api/run-cloudas well, or place it once before the route dispatch for allPOSThandlers.Proposed fix
if (req.method === 'POST' && pathname === '/api/run-cloud') { + if (rejectCrossOriginRequest(req, res)) return const body = await readJsonBody(req, res)🤖 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.ts` around lines 175 - 176, Apply rejectCrossOriginRequest to the POST /api/run-cloud handler as well as /api/schedule-cloud, or move the guard to a shared location covering every POST route before dispatch. Preserve existing route behavior while ensuring both token-backed Cloud mutation endpoints reject cross-origin requests.packages/local-runner/src/schedule-in-cloud.ts (1)
99-114: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMove the init check behind the project lookup.
The doc comment states that only the create path loses the init designation. An existing Cloud project keeps its designation, and
addNotebookToExistingProjectdoes not touch it. But the assertion runs beforefindProject, so a file withinitNotebookIdis refused even when the project already exists in Deepnote and only the notebook is missing. That path is safe, and the error text ("Import the project into Deepnote first") then names an action the user already did.Run the assertion only when no project is found.
Proposed fix
- assertInitNotebookSurvivesCreation(file) - // A matching project can exist without this notebook. Add to it rather than creating a // duplicate project; the coordinator serializes this check with cloud runs and schedules. const project = await findProject(baseUrl, token, file.project.name) + if (!project) { + assertInitNotebookSurvivesCreation(file) + } const createdTarget = await createFromFile(Add a test for an existing project with a missing notebook in an init-backed file.
🤖 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/schedule-in-cloud.ts` around lines 99 - 114, Move the assertInitNotebookSurvivesCreation call to after findProject and invoke it only when no existing project is found, preserving the safe existing-project path through createFromFile. Add a test covering an init-backed file whose project exists but whose notebook is missing, verifying the notebook is added without triggering the assertion.packages/local-runner/src/run-in-cloud.ts (1)
340-364: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winBuilding specs for every notebook wastes work and can emit unrelated warnings.
allNotebookSpecsmaps overtoCreate.project.notebooksand runstoBlockSpec(block, options.onWarning)for every notebook's blocks, unconditionally. Whendestinationis set, onlyallNotebookSpecs[index](the target notebook) is used inaddNotebookToExistingProject; the specs for every other notebook in the file are still built. IftoBlockSpecemits a warning for problematic metadata (per the retrieved learning about dropping invalid metadata with a warning), a user adding one notebook to an existing project can see warnings about blocks in unrelated sibling notebooks that are not being touched by this operation. This is misleading and wastes computation.Build the full
allNotebookSpecsarray only for thecreateProject(no-destination) path, and compute a spec for just the target notebook whendestinationis set.🐛 Proposed fix to scope spec building to the target notebook when extending an existing project
- const allNotebookSpecs: ProjectSpec['notebooks'] = toCreate.project.notebooks.map((notebook, i) => ({ - // The file's own id for this notebook, so `rewriteBlock` below can turn a block's reference to - // it into the id Deepnote assigns. - sourceId: notebook.id, - name: notebook.name, - blocks: sortedBlocks[i].map(block => toBlockSpec(block, options.onWarning)), - })) - const spec: ProjectSpec = { - name: toCreate.project.name, - notebooks: allNotebookSpecs, - } - const createOptions = { onProgress: options.onCreateProgress, onWarning: options.onWarning, rewriteBlock: rewriteNotebookFunctionId, } - const result = destination - ? await addNotebookToExistingProject(baseUrl, token, destination, toCreate, index, allNotebookSpecs[index], { - ...createOptions, - }) - : await createProject(baseUrl, token, spec, createOptions) - - const match = result.notebooks[destination ? 0 : index] + const toNotebookSpec = (notebook: (typeof toCreate.project.notebooks)[number], i: number): ProjectSpec['notebooks'][number] => ({ + // The file's own id for this notebook, so `rewriteBlock` below can turn a block's reference to + // it into the id Deepnote assigns. + sourceId: notebook.id, + name: notebook.name, + blocks: sortedBlocks[i].map(block => toBlockSpec(block, options.onWarning)), + }) + + const result = destination + ? await addNotebookToExistingProject( + baseUrl, + token, + destination, + toCreate, + index, + toNotebookSpec(toCreate.project.notebooks[index], index), + { ...createOptions } + ) + : await createProject( + baseUrl, + token, + { name: toCreate.project.name, notebooks: toCreate.project.notebooks.map(toNotebookSpec) }, + createOptions + ) + + const match = result.notebooks[destination ? 0 : index]🤖 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 340 - 364, Update the spec-building flow around allNotebookSpecs so destination mode converts only toCreate.project.notebooks[index] via toBlockSpec and passes that single target spec to addNotebookToExistingProject. Build the complete allNotebookSpecs array only in the createProject path, preserving full-project conversion and warnings when destination is unset.Source: Learnings
🧹 Nitpick comments (1)
packages/cli/src/commands/schedule.test.ts (1)
83-85: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDrop the unchecked cast.
tempDirs.pop() as stringhides theundefinedcase from the type checker. Iterate the drained array instead.Proposed refactor
- while (tempDirs.length > 0) { - rmSync(tempDirs.pop() as string, { recursive: true, force: true }) - } + for (const dir of tempDirs.splice(0)) { + rmSync(dir, { recursive: true, force: true }) + }As per coding guidelines: "prefer type safety over convenience".
🤖 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/schedule.test.ts` around lines 83 - 85, Update the temporary-directory cleanup loop to avoid the unchecked string cast on tempDirs.pop(). Drain and iterate the array using a type-safe approach that handles the empty/undefined case while preserving recursive forced removal for each directory.Source: Coding guidelines
🤖 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/utils/schedule-expression.test.ts`:
- Around line 6-8: Add exact-output test cases to the hourly schedule-expression
cases for parseHourlyMinute boundary inputs `--at :00` and `--at :59`, asserting
they produce `0 * * * *` and `59 * * * *` respectively while preserving the
existing test structure and descriptions.
In `@packages/local-runner/src/run-in-cloud.ts`:
- Around line 172-178: Update the runInCloud creation flow around createFromFile
to perform the same pre-creation init-notebook existence assertion used by
scheduleInCloud, preserving file.project.initNotebookId before creating content.
Ensure the not-found case fails before createFromFile runs, and add a regression
test covering that path.
---
Outside diff comments:
In `@packages/cli/src/utils/schedule-expression.ts`:
- Around line 71-78: Update the cron validation in the options.cron handling
path to validate each of the five fields against the cron grammar accepted by
Deepnote Cloud, not only the field count. Reject invalid tokens before
scheduleInCloud is reached, preserve the existing empty and incorrect-count
errors, and add tests covering malformed cron fields such as “invalid foo bar
baz qux”.
In `@packages/local-runner/src/run-in-cloud.ts`:
- Around line 340-364: Update the spec-building flow around allNotebookSpecs so
destination mode converts only toCreate.project.notebooks[index] via toBlockSpec
and passes that single target spec to addNotebookToExistingProject. Build the
complete allNotebookSpecs array only in the createProject path, preserving
full-project conversion and warnings when destination is unset.
In `@packages/local-runner/src/schedule-in-cloud.ts`:
- Around line 99-114: Move the assertInitNotebookSurvivesCreation call to after
findProject and invoke it only when no existing project is found, preserving the
safe existing-project path through createFromFile. Add a test covering an
init-backed file whose project exists but whose notebook is missing, verifying
the notebook is added without triggering the assertion.
In `@packages/local-runner/src/serve-static.ts`:
- Around line 175-176: Apply rejectCrossOriginRequest to the POST /api/run-cloud
handler as well as /api/schedule-cloud, or move the guard to a shared location
covering every POST route before dispatch. Preserve existing route behavior
while ensuring both token-backed Cloud mutation endpoints reject cross-origin
requests.
---
Nitpick comments:
In `@packages/cli/src/commands/schedule.test.ts`:
- Around line 83-85: Update the temporary-directory cleanup loop to avoid the
unchecked string cast on tempDirs.pop(). Drain and iterate the array using a
type-safe approach that handles the empty/undefined case while preserving
recursive forced removal for each directory.
🪄 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: 19c156b1-f4d5-49d2-8303-7583924e04ec
📒 Files selected for processing (17)
packages/cli/README.mdpackages/cli/src/cli.tspackages/cli/src/commands/schedule.test.tspackages/cli/src/commands/schedule.tspackages/cli/src/utils/schedule-expression.test.tspackages/cli/src/utils/schedule-expression.tspackages/local-runner/README.mdpackages/local-runner/src/cloud-notebook-coordinator.test.tspackages/local-runner/src/cloud-notebook-coordinator.tspackages/local-runner/src/recurring-schedule.test.tspackages/local-runner/src/run-in-cloud.test.tspackages/local-runner/src/run-in-cloud.tspackages/local-runner/src/schedule-in-cloud.test.tspackages/local-runner/src/schedule-in-cloud.tspackages/local-runner/src/serve-static.test.tspackages/local-runner/src/serve-static.tsskills/deepnote/references/cli-schedule.md
🚧 Files skipped from review as they are similar to previous changes (7)
- packages/cli/README.md
- skills/deepnote/references/cli-schedule.md
- packages/cli/src/cli.ts
- packages/local-runner/src/cloud-notebook-coordinator.test.ts
- packages/local-runner/README.md
- packages/local-runner/src/cloud-notebook-coordinator.ts
- packages/cli/src/commands/schedule.ts
findProject returned the newest exact-name project regardless of its type. The public spec types projects as standard | notebook | agent, and Deepnote rejects notebook creation in the latter two — so a newer single-notebook or Agent project shadowed an older usable one and turned createIfMissing into HTTP 409. Preserve projectType through the parser and skip the closed types. When every match is closed, report no destination: the caller then creates a fresh standard project, which is the outcome that works. An unreported type stays usable, since refusing what we cannot read would bring back the duplicate projects this lookup exists to prevent. Enum values from the public OpenAPI spec at https://api.deepnote.com/v2/openapi.json. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Correcting my previous comment on the two "needs input" items — I had only searched this repo, not the public API docs. The spec at [P2] Select a project that accepts additional notebooks — now fixed in "projectType": {
"description": "Project type. Omit to create a `standard` project; `notebook` creates a single-notebook project; `agent` creates an Agent project.",
"type": "string",
"enum": ["standard", "notebook", "agent"]
}It is a required field on the project response.
[P2] Align first-schedule safety defaults — confirmed out of scope for this repo, with stronger evidence than I had. {
"type": "object",
"properties": { "cron": {...}, "timezone": { "default": "UTC", ... } },
"required": ["cron"],
"additionalProperties": false
}
Full suite green (2767 tests). Still draft. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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.test.ts`:
- Around line 237-245: Add a test alongside the existing untyped-project case
that returns a project with an unrecognized projectType such as "future-type",
then verify findProject still returns that project as usable. Keep the same
fetch mock and expected project result pattern, while ensuring the unknown value
is explicitly present.
In `@skills/deepnote/references/cli-schedule.md`:
- Around line 63-65: Update the project-selection documentation around the
`notebook` and `agent` exclusions to state that those known types are skipped
while unknown project types remain eligible. Align the wording with the
implementation rather than claiming that only `standard` projects can accept a
new notebook.
🪄 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: 6af98cd5-4c2e-4d97-b015-d586a0b1312a
📒 Files selected for processing (3)
packages/cloud/src/projects.test.tspackages/cloud/src/projects.tsskills/deepnote/references/cli-schedule.md
… to new projects Three fixes from review: The origin guard covered /api/schedule-cloud only. POST /api/run-cloud spends the same cloud token and creates project content when the notebook is not in Deepnote yet, so the DNS-rebinding path the guard was added to close was still open on it. The init-notebook refusal ran before findProject, so an init-backed file was refused even when its project was already in Deepnote and only the notebook was missing — a path that loses nothing, and whose error told the user to import a project they had already imported. Moved into createFromFile behind the create-a-new-project branch, which also extends it to runInCloud, whose create path drops the designation just as silently. Building specs for every notebook when only one is being added meant toBlockSpec warned about blocks in siblings the operation never touches. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@coderabbitai the four outside-diff-range findings, addressed in
Full suite green (2773 tests). |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
git show --no-ext-diff --stat --oneline 4d64e0e
git show --no-ext-diff --format= -- \
packages/local-runner/src/serve-static.ts \
packages/local-runner/src/serve-static.test.ts \
packages/local-runner/src/schedule-in-cloud.ts \
packages/local-runner/src/run-in-cloud.ts \
packages/local-runner/src/run-in-cloud.test.ts \
packages/cli/src/commands/schedule.ts \
packages/cli/src/utils/schedule-expression.ts \
4d64e0e
printf '\n--- outlines ---\n'
ast-grep outline packages/local-runner/src/serve-static.ts --items all --type function
ast-grep outline packages/local-runner/src/run-in-cloud.ts --items all --type function
ast-grep outline packages/local-runner/src/schedule-in-cloud.ts --items all --type function
printf '\n--- targeted references ---\n'
rg -n -C 4 \
'run-cloud|schedule-cloud|Origin|localPort|createFromFile|initNotebookId|specFor|toBlockSpec|statusCode|ScheduleExpressionError|ApiError' \
packages/local-runner/src/serve-static.ts \
packages/local-runner/src/serve-static.test.ts \
packages/local-runner/src/schedule-in-cloud.ts \
packages/local-runner/src/run-in-cloud.ts \
packages/local-runner/src/run-in-cloud.test.ts \
packages/cli/src/commands/schedule.ts \
packages/cli/src/utils/schedule-expression.tsLength of output: 73630
I also withdraw the cron field-validation finding. The CLI validates empty and non-five-field cron input. The API validates its authoritative cron dialect. HTTP 400 maps to ✏️ Learnings added
You are interacting with an AI system. |
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/local-runner/src/schedule-in-cloud.test.ts`:
- Around line 286-288: Update the test around the second upsertNotebookSchedule
invocation to assert that it receives the scheduled notebook ID
`notebook-created`, in addition to the existing result and call-count
assertions. Use the mock’s recorded calls to verify the second call’s notebook
ID while preserving the current expectations.
🪄 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: 19f470e7-5c9e-4d66-bc6e-7536cac50b9d
📒 Files selected for processing (7)
packages/cli/src/utils/schedule-expression.test.tspackages/local-runner/src/run-in-cloud.test.tspackages/local-runner/src/run-in-cloud.tspackages/local-runner/src/schedule-in-cloud.test.tspackages/local-runner/src/schedule-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)
- packages/cli/src/utils/schedule-expression.test.ts
- packages/local-runner/src/serve-static.test.ts
- packages/local-runner/src/schedule-in-cloud.ts
- packages/local-runner/src/run-in-cloud.ts
findProject skips only the types known to reject notebook creation, so a type this client has not heard of stays eligible. That was deliberate but only the absent-field case was covered, leaving a later restrictive change free to break it silently. The schedule reference said only standard projects qualify, which did not match. Also assert the retried schedule names the newly created notebook, rather than trusting the call count. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dinohamzic
left a comment
There was a problem hiding this comment.
Second round, not much left (I'm staring manual testing next):
[P1] Do not treat an unresolved sibling init notebook as absent
packages/local-runner/src/run-in-cloud.ts:457-460
When initNotebookId references a notebook outside the current file, this branch returns successfully and creation continues without the init notebook.
This is a supported file shape: splitByNotebooks deliberately preserves initNotebookId in each main file so the sibling resolver can find the standalone init file (packages/convert/src/snapshot/split.test.ts:654-660). However, the schedule command directly deserializes the selected file (packages/cli/src/commands/schedule.ts:62-65) and never calls resolveAndComposeInitIfNeeded.
Consequently, scheduling a split main file can create and schedule it without its required setup. Please resolve sibling init notebooks before scheduling, or fail closed whenever a declared init ID cannot be resolved.
[P1] An exact-name destination does not prove the init setup is present
packages/local-runner/src/run-in-cloud.ts:339-341
The init-preservation check is skipped whenever findProject returns an existing exact-name project. The public project response does not expose whether that project has an init designation, and only the target notebook is uploaded.
The new test at packages/local-runner/src/schedule-in-cloud.test.ts:272-298 demonstrates the unsafe case: it accepts an existing project with notebooks: [] and schedules the newly added main notebook. Internally, scheduled execution prepends setup only when project.init_notebook_id is set (deepnote-internal/apps/webapp/server/modules/scheduling/run-scheduled-executions.ts:574-579).
Therefore, an unrelated same-name project—or one whose init designation was removed—will run the scheduled notebook without setup. Please fail closed unless the API can verify or establish the destination’s init designation.
[P2] Spread daily, weekly, and monthly defaults too
packages/cli/src/utils/schedule-expression.ts:97
Hourly schedules now use the creation minute, but every daily, weekly, and monthly schedule without --at still defaults to 09:00.
The internal scheduler intentionally defaults new schedules to the current hour and minute to avoid execution spikes (deepnote-internal/apps/webapp-client/src/notebook/features/scheduling/containers/SchedulingModalContainer.tsx:72-74). The CLI should use the same strategy for all generated schedules, not only hourly ones.
[P2] Match the UI’s first-schedule defaults
packages/cloud/src/schedules.ts:27-32
The client can send only cron and timezone. The internal strict request schema has the same limitation, and the create path consequently relies on database defaults:
republish = falseauto_pause_on_failure = false
The product UI defaults both settings to true (SchedulingModalContainer.tsx:182-190). Thus, creating the first schedule through the CLI silently behaves differently from creating it through Deepnote: published apps are not republished and repeatedly failing schedules are not automatically paused.
Please extend the internal endpoint and client to apply the product defaults when creating a schedule. Existing schedule updates should continue preserving the user’s configured values.
Confirmed fixed
The latest commits correctly address:
- caller-specific inputs leaking into shared project creation;
- target block-ID isolation;
- ambiguous sibling notebook-function mappings;
- project-type selection;
- invalid-file exit-code handling;
- hourly minute spreading and explicit
--at; - monthly days 1–31;
- socket-based origin validation for the cloud mutation routes.
…dule default Two P1s from review, which collapse into one rule: creating content for a file that declares an initNotebookId is never safe, so refuse whenever it is set. An id that resolves to nothing in the file is not an absent init. splitByNotebooks deliberately keeps initNotebookId in every main file so the sibling resolver can find the standalone init file, and the schedule command deserializes the selected file without composing it — so the previous "names nothing here, nothing to lose" branch scheduled split main files without their setup. Composing would not have helped either: the composed [init, main] still needs a designation this API cannot set. An existing exact-name project is not evidence either. Only the target notebook is uploaded into it, and the API will not report whether that project carries an init designation, so an unrelated project sharing the name runs the notebook without setup just the same. The destination gate is gone. Separately, only hourly schedules were being spread. Deepnote's scheduling UI defaults every new schedule to the current hour and minute for the same reason, so daily, weekly and monthly now use the creation time instead of 09:00. Co-Authored-By: Claude Opus 5 (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/utils/schedule-expression.test.ts`:
- Around line 24-28: Update the test “defaults to the system timezone” to
compare result.timezone from resolveScheduleExpression({ daily: true }) against
the documented system-timezone value, replacing the non-specific toBeTruthy
assertion and preserving the existing test setup.
🪄 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: 935f4059-b600-46a6-8e28-43772b8aa2f7
📒 Files selected for processing (9)
packages/cli/README.mdpackages/cli/src/cli.tspackages/cli/src/utils/schedule-expression.test.tspackages/cli/src/utils/schedule-expression.tspackages/local-runner/README.mdpackages/local-runner/src/run-in-cloud.test.tspackages/local-runner/src/run-in-cloud.tspackages/local-runner/src/schedule-in-cloud.test.tsskills/deepnote/references/cli-schedule.md
🚧 Files skipped from review as they are similar to previous changes (8)
- skills/deepnote/references/cli-schedule.md
- packages/cli/src/cli.ts
- packages/cli/README.md
- packages/local-runner/src/schedule-in-cloud.test.ts
- packages/cli/src/utils/schedule-expression.ts
- packages/local-runner/src/run-in-cloud.ts
- packages/local-runner/README.md
- packages/local-runner/src/run-in-cloud.test.ts
|
@dinohamzic second round handled — three fixed in [P1] Unresolved sibling init notebook — fixedYou're right, and the old branch was worse than "returns successfully": its comment asserted "the designation names nothing in this file, so there is no setup to lose", which is exactly the wrong reading of the split shape. Confirmed against Worth noting composing would not have fixed it either: [P1] Exact-name destination proves nothing — fixedAgreed, and this reverses a change I made last round on CodeRabbit's suggestion that the existing-project path was safe. Your reasoning wins: only the target notebook is uploaded, and the API will not report the destination's designation, so a same-name project — or one whose designation was removed — runs without setup. Both P1s now collapse into one rule in [P2] Spread daily/weekly/monthly — fixedFair — spreading only hourly was half a fix. All cadences now default to the creation time, matching [P2] First-schedule safety defaults — verified, not actioned hereI verified your claim exhaustively against the published spec rather than assuming, and it holds in the strongest form:
So there is no way to close this from this repo — it needs the internal create branch to apply the product defaults, which is your call to sequence rather than something I should land unilaterally in a different repo. Flagging one thing for whoever picks it up: Happy to wire the client side here the moment the contract carries the fields. CI note: |
|
CI follow-up: the Refining what I said about that one: Everything else is green: 20/21 checks, full suite 2779 tests. |
…y one toBeTruthy() passed for any non-empty string, so the test would have gone on passing if resolveTimezone stopped reading the system clock and hardcoded a fallback. Compare against the zone Intl actually resolves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dinohamzic
left a comment
There was a problem hiding this comment.
Even when using --hourly to schedule via CLI, in Deepnote I then see "Cron (custom)" as the selected option instead of "Hourly".
It would be much more user friendly if --hourly, --daily, --weekly etc. matched the corresponding option in Deepnote Cloud.
I was not able to spot any other functional issues while manually testing, nice one.
|
@coderabbitai open an issue describing this difference to deepnote cloud as something to fix later #437 (review) |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
gh api repos/deepnote/deepnote/pulls/comments/4853245965 \
--jq '{id, html_url, body, path, line, user: .user.login}'
gh pr view 437 --repo deepnote/deepnote \
--json url,title \
--jq '{url,title}'Length of output: 712 🏁 Script executed: #!/bin/bash
set -euo pipefail
gh api repos/deepnote/deepnote/pulls/437/reviews/4853245965 \
--jq '{id, html_url, body, user: .user.login}'
gh pr view 437 --repo deepnote/deepnote \
--json url,title \
--jq '{url,title}'Length of output: 1114 🏁 Script executed: #!/bin/bash
set -euo pipefail
ISSUE_TITLE="$(cat <<'EOF'
Preserve friendly schedule cadence for API-created notebook schedules
EOF
)"
ISSUE_BODY="$(cat <<'EOF'
## Summary
Notebook schedules created through the public API use a cron expression. When the CLI creates a schedule with `--hourly`, `--daily`, `--weekly`, or `--monthly`, Deepnote Cloud displays the schedule type as `Cron (custom)` instead of the matching friendly cadence.
Deepnote Cloud should preserve or recognize the friendly cadence for schedules created through the API.
## Rationale
The CLI presents friendly cadence options. The Deepnote Cloud UI should show the same cadence after the schedule is created. A `Cron (custom)` display makes a standard schedule appear different from the selected CLI option.
## Affected areas
- Deepnote Cloud notebook schedule creation and display
- Public schedule API contract
- Schedule persistence or schedule-type inference
## References
- PR: [`#437`](https://github.com/deepnote/deepnote/pull/437)
- Review: [pullrequestreview-4853245965](https://github.com/deepnote/deepnote/pull/437#pullrequestreview-4853245965)
- Requested by: `@jamesbhobbs`
## Acceptance criteria
- [ ] A schedule created with the API from an hourly cadence displays as `Hourly` in Deepnote Cloud.
- [ ] A schedule created with the API from daily, weekly, and monthly cadences displays as the matching cadence in Deepnote Cloud.
- [ ] A genuinely custom cron expression continues to display as `Cron (custom)`.
- [ ] The public API contract documents the supported behavior. If cadence metadata is required, the API accepts and returns it.
EOF
)"
issue_url="$(
gh issue create \
--repo deepnote/deepnote \
--title "$ISSUE_TITLE" \
--body "$ISSUE_BODY" \
--assignee jamesbhobbs
)"
printf '%s\n' "$issue_url"Length of output: 2002
You are interacting with an AI system. |
Summary
Behavior
Scheduling configures the project recurring Cloud schedule without immediately running the notebook. Deepnote supports one scheduled notebook per project, so invoking the command again updates or re-points that schedule.
This work is separate from the pipeline/orchestration changes.
Validation
Conflict note
Most implementation is in new files. The small CLI registration, completion, documentation, and Cloud export overlaps with #433 may require a minor conflict resolution depending on merge order.
Summary by CodeRabbit
deepnote schedule <path>for recurring hourly, daily, weekly, monthly, or cron-based Cloud runs.