mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-08 00:15:12 +00:00
fix(cli): filter unknown --inputs keys even on flow persistence restore
Review follow-up: the `id` (persistence-restore) branch of _resolve_flow_inputs returned the raw payload, so typo keys passed alongside `id` skipped the unknown-key warning/drop and reached kickoff — which can fail strict (extra="forbid") flow state models. The restore path now still warns on and drops unknown keys (keeping `id` and known state fields); it only skips the required-field prompt and pre-kickoff validation, which persistence hydrates. Regression test: test_id_restore_still_drops_unknown_keys. Also drop the duplicate module import in test_input_prompt.py (both `import` and `from ... import` of crewai_cli.input_prompt) flagged by the code-quality bot; monkeypatching now uses the string target form. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RBYGqJHC2TMC6fonFziuuh
This commit is contained in:
@@ -89,13 +89,6 @@ def _resolve_flow_inputs(flow: Any, provided: dict[str, Any]) -> dict[str, Any]:
|
||||
# dict / unschematized state — nothing to derive; pass inputs through.
|
||||
return dict(provided)
|
||||
|
||||
# ``id`` signals a persistence restore: kickoff hydrates the full state from
|
||||
# storage, so required fields may come from the restored state rather than
|
||||
# --inputs. Forward the inputs unchanged instead of prompting/erroring for
|
||||
# fields the resume will supply.
|
||||
if "id" in provided:
|
||||
return dict(provided)
|
||||
|
||||
properties = {
|
||||
name: spec
|
||||
for name, spec in (schema.get("properties") or {}).items()
|
||||
@@ -104,10 +97,21 @@ def _resolve_flow_inputs(flow: Any, provided: dict[str, Any]) -> dict[str, Any]:
|
||||
state_model = type(flow.state)
|
||||
defaults = _flow_state_defaults(flow)
|
||||
|
||||
# ``id`` signals a persistence restore: kickoff hydrates the full state from
|
||||
# storage, so required fields may come from the restored state rather than
|
||||
# --inputs. We still filter the rest of the payload below, but skip the
|
||||
# required-field prompt and pre-kickoff validation, which would otherwise
|
||||
# fail on fields the resume will supply.
|
||||
restoring = "id" in provided
|
||||
|
||||
# Unknown keys are almost always typos — warn and drop them (they'd fail
|
||||
# structured-state validation at kickoff anyway).
|
||||
# structured-state validation at kickoff anyway). ``id`` is a reserved
|
||||
# kickoff key rather than a state field, so forward it untouched.
|
||||
collected: dict[str, Any] = {}
|
||||
for key, value in provided.items():
|
||||
if key == "id":
|
||||
collected["id"] = value
|
||||
continue
|
||||
if key in properties:
|
||||
collected[key] = value
|
||||
continue
|
||||
@@ -119,6 +123,9 @@ def _resolve_flow_inputs(flow: Any, provided: dict[str, Any]) -> dict[str, Any]:
|
||||
err=True,
|
||||
)
|
||||
|
||||
if restoring:
|
||||
return collected
|
||||
|
||||
missing = _missing_required(state_model, {**defaults, **collected})
|
||||
if missing and _is_interactive():
|
||||
collected.update(
|
||||
|
||||
@@ -4,7 +4,6 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
import crewai_cli.input_prompt as input_prompt_module
|
||||
from crewai_cli.input_prompt import (
|
||||
closest_name,
|
||||
parse_inputs_json,
|
||||
@@ -51,7 +50,7 @@ def test_prompt_for_inputs_uses_describe_and_coerce(monkeypatch, capsys):
|
||||
seen.append(text)
|
||||
return "42"
|
||||
|
||||
monkeypatch.setattr(input_prompt_module.click, "prompt", fake_prompt)
|
||||
monkeypatch.setattr("crewai_cli.input_prompt.click.prompt", fake_prompt)
|
||||
|
||||
result = prompt_for_inputs(
|
||||
["count"],
|
||||
@@ -71,7 +70,7 @@ def test_prompt_for_inputs_uses_describe_and_coerce(monkeypatch, capsys):
|
||||
|
||||
def test_prompt_for_inputs_keeps_raw_string_without_coerce(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
input_prompt_module.click, "prompt", lambda text, **kwargs: "AI"
|
||||
"crewai_cli.input_prompt.click.prompt", lambda text, **kwargs: "AI"
|
||||
)
|
||||
|
||||
result = prompt_for_inputs(
|
||||
|
||||
@@ -381,3 +381,22 @@ def test_id_only_input_skips_required_validation(tmp_path: Path) -> None:
|
||||
resolved = run_declarative_flow_module._resolve_flow_inputs(flow, {"id": "run-123"})
|
||||
|
||||
assert resolved == {"id": "run-123"}
|
||||
|
||||
|
||||
def test_id_restore_still_drops_unknown_keys(
|
||||
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
# A persistence restore (`id` present) still filters typo keys so they don't
|
||||
# reach kickoff and trip strict (extra="forbid") state models — it only
|
||||
# skips the required-field prompt/validation, not the unknown-key warning.
|
||||
path = _write(tmp_path, REQUIRED_FLOW_YAML)
|
||||
flow = run_declarative_flow_module.load_declarative_flow(str(path))
|
||||
|
||||
resolved = run_declarative_flow_module._resolve_flow_inputs(
|
||||
flow, {"id": "run-123", "prospect_emai": "typo"}
|
||||
)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert resolved == {"id": "run-123"} # id kept, typo dropped
|
||||
assert "Ignoring unknown input 'prospect_emai'" in captured.err
|
||||
assert "Ignoring unknown input 'id'" not in captured.err
|
||||
|
||||
Reference in New Issue
Block a user