fix(cli): apply input overrides through input blocks - #418
Conversation
📝 WalkthroughWalkthroughAdds block-aware input override validation and parsing for 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
🚥 Pre-merge checks | ✅ 5 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (5 passed)
Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/blocks/src/blocks/input-blocks.ts (1)
244-289: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winInconsistent strictness: object/array inputs silently become garbage strings.
Checkbox and date-range validate shape and throw
InvalidValueErroron bad input, but slider/text/textarea/date/file/select fall back tocoerceToInputString, which blindly callsString(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
📒 Files selected for processing (5)
packages/blocks/src/blocks/input-blocks.test.tspackages/blocks/src/blocks/input-blocks.tspackages/blocks/src/index.tspackages/cli/src/commands/run.test.tspackages/cli/src/commands/run.ts
feb76b0 to
ee6e6a7
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
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/runtime-core/src/execution-engine.ts (1)
476-526: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftAvoid re-running input blocks
INPUT_BLOCK_TYPESare included inisExecutableBlock, so matched input blocks run once ininjectInputsand then again inrunProject’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 winNumeric-string check is stricter than typical slider input.
isFiniteNumberStringrequires the raw string to round-trip exactly throughNumber.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
📒 Files selected for processing (10)
packages/blocks/src/blocks/input-blocks.test.tspackages/blocks/src/blocks/input-blocks.tspackages/blocks/src/index.tspackages/cli/README.mdpackages/cli/src/cli.tspackages/cli/src/commands/run.test.tspackages/cli/src/commands/run.tspackages/runtime-core/src/execution-engine.test.tspackages/runtime-core/src/execution-engine.tsskills/deepnote/references/cli-run.md
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>
Problem
deepnote run --input count=7previously JSON-parsed every CLI value, turning7into 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:
trueorfalseDuring execution,
ExecutionEngineapplies the value to each matching input block and calls the existingcreatePythonCode(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
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
deepnote run --input.Documentation
Bug Fixes