Skip to content

fix(cli): apply input overrides through input blocks - #418

Merged
dinohamzic merged 6 commits into
mainfrom
fix/input-coercion-snapshot
Jul 13, 2026
Merged

fix(cli): apply input overrides through input blocks#418
dinohamzic merged 6 commits into
mainfrom
fix/input-coercion-snapshot

Conversation

@jamesbhobbs

@jamesbhobbs jamesbhobbs commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Problem

deepnote run --input count=7 previously JSON-parsed every CLI value, turning 7 into a number. Slider input blocks store "7" and generate the numeric Python assignment themselves, so writing the number into block metadata caused execution snapshot serialization to fail.

Runtime input injection also bypassed the normal input-block Python generation path.

Fix

Resolve each CLI input name against the input blocks in the selected notebook scope:

  • text, textarea, file, date, slider, and single-select inputs use plain strings
  • checkbox inputs accept true or false
  • multi-select inputs and absolute date ranges accept JSON arrays of strings
  • unknown input names and invalid values are rejected before execution
  • values used by multiple same-name blocks must be accepted by every matching block

During execution, ExecutionEngine applies the value to each matching input block and calls the existing createPythonCode(block) path. Inputs without a matching block keep the existing generic Python-literal behavior for programmatic callers.

The executed project retains the values expected by its input blocks, allowing snapshots to serialize normally.

Tests

  • input value checks for every input block type
  • CLI coverage for strings, checkboxes, multi-selects, date ranges, unknown names, invalid values, notebook scope, and duplicate variable names
  • runtime coverage for input-block Python generation, metadata updates, invalid values, and the generic fallback
  • regression coverage for serializing a snapshot after overriding a slider

pnpm test, pnpm typecheck, Biome, and Prettier pass.

Notes

Base of a stack: #417 (deepnote run --cloud) and #419 (@deepnote/local-runner) build on this PR. Review/merge order: #418#417#419.

Summary by CodeRabbit

  • New Features

    • Added type-aware input values for deepnote run --input.
    • Supports text, slider, checkbox, date-range, single-select, and multi-select inputs.
    • Rejects unknown input names and invalid value formats before execution.
    • Preserves input overrides in generated runs and supports scoped notebook inputs.
  • Documentation

    • Updated CLI help and guides with accepted input formats and examples.
  • Bug Fixes

    • Improved handling of empty values, equals signs, and string-like numeric or boolean values.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds block-aware input override validation and parsing for deepnote run. CLI values are matched to scoped input blocks, validated by block type, and rejected when names or shapes are invalid. Runtime execution writes validated overrides into input block metadata and injects corresponding assignments, while preserving generic inputs without matching blocks. Public exports, tests, fixtures, and CLI documentation are updated.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as deepnote run
  participant Parser as parseInputs
  participant Validator as input block validator
  participant Runtime as runProject
  participant Python as Execution engine
  CLI->>Parser: Parse --input name=value
  Parser->>Validator: Validate value for matching block
  Validator-->>Parser: Return validated override
  Parser->>Runtime: Pass InputBlockValueOverrides
  Runtime->>Python: Inject metadata and assignments
  Python-->>Runtime: Return execution result
Loading
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Updates Docs ✅ Passed README, CLI help, and run reference now document the new --input shapes.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main CLI change: applying input overrides through input blocks.

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.71%. Comparing base (b521d4e) to head (4219a04).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #418      +/-   ##
==========================================
+ Coverage   86.50%   86.71%   +0.20%     
==========================================
  Files         160      160              
  Lines        8380     8435      +55     
  Branches     2330     2296      -34     
==========================================
+ Hits         7249     7314      +65     
+ Misses       1130     1120      -10     
  Partials        1        1              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/blocks/src/blocks/input-blocks.ts (1)

244-289: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Inconsistent strictness: object/array inputs silently become garbage strings.

Checkbox and date-range validate shape and throw InvalidValueError on bad input, but slider/text/textarea/date/file/select fall back to coerceToInputString, which blindly calls String(value). An object override silently becomes "[object Object]", and an array becomes "a,b" — no error, just corrupted metadata.

♻️ Proposed guard
 function coerceToInputString(value: unknown): string {
-  return value === null || value === undefined ? '' : String(value)
+  if (value === null || value === undefined) return ''
+  if (typeof value === 'object') {
+    throw new InvalidValueError(`Invalid input value: expected a scalar, received an object/array.`, { value })
+  }
+  return String(value)
 }
🤖 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/blocks/src/blocks/input-blocks.ts` around lines 244 - 289, Update
coerceToInputString and the scalar branches in coerceInputVariableValue to
reject non-scalar objects and arrays with InvalidValueError instead of
converting them via String(value); preserve nullish-to-empty-string behavior and
valid primitive coercion. Apply the same validation to single-value select
handling while retaining its existing multi-value array semantics, and include
the received value in the error details.
🤖 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/blocks/src/blocks/input-blocks.ts`:
- Around line 244-289: Update coerceToInputString and the scalar branches in
coerceInputVariableValue to reject non-scalar objects and arrays with
InvalidValueError instead of converting them via String(value); preserve
nullish-to-empty-string behavior and valid primitive coercion. Apply the same
validation to single-value select handling while retaining its existing
multi-value array semantics, and include the received value in the error
details.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 42a520ef-ad9e-4e8a-87b2-7d5d83c88ad0

📥 Commits

Reviewing files that changed from the base of the PR and between b521d4e and feb76b0.

📒 Files selected for processing (5)
  • packages/blocks/src/blocks/input-blocks.test.ts
  • packages/blocks/src/blocks/input-blocks.ts
  • packages/blocks/src/index.ts
  • packages/cli/src/commands/run.test.ts
  • packages/cli/src/commands/run.ts

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 10, 2026
`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>
@jamesbhobbs
jamesbhobbs force-pushed the fix/input-coercion-snapshot branch from ee6e6a7 to ae895f2 Compare July 11, 2026 12:25
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/runtime-core/src/execution-engine.ts (1)

476-526: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Avoid re-running input blocks

INPUT_BLOCK_TYPES are included in isExecutableBlock, so matched input blocks run once in injectInputs and then again in runProject’s main loop. That doubles the kernel execution for overridden inputs and can skew snapshot metadata. If multiple blocks share a variable name, the same assignment is also emitted once per block.

🤖 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/runtime-core/src/execution-engine.ts` around lines 476 - 526, Update
runProject’s main execution loop to skip blocks matched by injectInputs,
preventing overridden input blocks from executing twice. In injectInputs, emit
each input variable assignment only once even when multiple blocks share the
same name, while still updating all matching blocks’ metadata and preserving
generic variable injection.
🧹 Nitpick comments (1)
packages/blocks/src/blocks/input-blocks.ts (1)

96-99: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Numeric-string check is stricter than typical slider input.

isFiniteNumberString requires the raw string to round-trip exactly through Number.parseFloat().toString(). This rejects perfectly valid decimal notations a CLI user might type: '5.0', '.5', '+5', '1e3' all fail (parsed .toString() normalizes them away from the input form). The '07' rejection test confirms this is partly intentional (leading-zero ambiguity), but the collateral rejection of common canonical forms looks like an overlooked edge case rather than a deliberate contract.

♻️ Suggested regex-based validation (keeps '07'/'Infinity' rejected, accepts '5.0', '-5.25', etc.)
-function isFiniteNumberString(value: string): boolean {
-  const parsed = Number.parseFloat(value)
-  return Number.isFinite(parsed) && value.trim() === parsed.toString()
-}
+function isFiniteNumberString(value: string): boolean {
+  const trimmed = value.trim()
+  if (!/^-?(0|[1-9]\d*)(\.\d+)?$/.test(trimmed)) {
+    return false
+  }
+  return Number.isFinite(Number.parseFloat(trimmed))
+}

Please confirm whether exact round-trip stability is actually required by the app's slider contract, or whether this is an unintentional side effect of the .toString() comparison.

🤖 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/blocks/src/blocks/input-blocks.ts` around lines 96 - 99, Clarify the
slider numeric-string contract around isFiniteNumberString: replace the exact
parseFloat/toString round-trip check with validation that accepts standard
finite decimal and exponent forms such as 5.0, .5, +5, and 1e3 while continuing
to reject ambiguous leading-zero values like 07 and non-finite values such as
Infinity. Update the associated tests to cover these accepted and rejected
forms.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/runtime-core/src/execution-engine.ts`:
- Around line 476-526: Update runProject’s main execution loop to skip blocks
matched by injectInputs, preventing overridden input blocks from executing
twice. In injectInputs, emit each input variable assignment only once even when
multiple blocks share the same name, while still updating all matching blocks’
metadata and preserving generic variable injection.

---

Nitpick comments:
In `@packages/blocks/src/blocks/input-blocks.ts`:
- Around line 96-99: Clarify the slider numeric-string contract around
isFiniteNumberString: replace the exact parseFloat/toString round-trip check
with validation that accepts standard finite decimal and exponent forms such as
5.0, .5, +5, and 1e3 while continuing to reject ambiguous leading-zero values
like 07 and non-finite values such as Infinity. Update the associated tests to
cover these accepted and rejected forms.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8a80b7f6-628b-4bed-9224-a092bae30398

📥 Commits

Reviewing files that changed from the base of the PR and between ae895f2 and bdc3ecb.

📒 Files selected for processing (10)
  • packages/blocks/src/blocks/input-blocks.test.ts
  • packages/blocks/src/blocks/input-blocks.ts
  • packages/blocks/src/index.ts
  • packages/cli/README.md
  • packages/cli/src/cli.ts
  • packages/cli/src/commands/run.test.ts
  • packages/cli/src/commands/run.ts
  • packages/runtime-core/src/execution-engine.test.ts
  • packages/runtime-core/src/execution-engine.ts
  • skills/deepnote/references/cli-run.md

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 13, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 13, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 13, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 13, 2026
@dinohamzic dinohamzic changed the title fix(cli): coerce input overrides to schema shape so snapshots serialize fix(cli): apply input overrides through input blocks Jul 13, 2026
@dinohamzic
dinohamzic marked this pull request as ready for review July 13, 2026 08:36
@dinohamzic
dinohamzic requested a review from a team as a code owner July 13, 2026 08:36
@dinohamzic
dinohamzic merged commit ef3907b into main Jul 13, 2026
20 of 21 checks passed
@dinohamzic
dinohamzic deleted the fix/input-coercion-snapshot branch July 13, 2026 11:37
jamesbhobbs added a commit that referenced this pull request Jul 13, 2026
PR #418 was squash-merged to main, so GitHub retargeted this PR from
fix/input-coercion-snapshot to main. This branch already contains 418 (via the
branch merge) plus the refactor that hoisted the block-aware input parser into
utils/parse-inputs.ts, so main's squashed copy conflicts with our moved code.

Main adds only the 418 squash on top of the merge-base, which we already have, so
our side is a superset: resolved by keeping ours in run.ts and the two docs. The
files 418 owns (blocks, runtime-core, the fixture) are byte-identical to main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants