mvanhorn/last30days-skill · error · ValueError
field '{field}' must be a non-empty string
Error message
field '{field}' must be a non-empty string What it means
Type/format check in validate_external_plan for the three string metadata fields (intent, freshness_mode, cluster_mode): each must be a str AND non-empty after strip(). Whitespace-only strings also fail. Values are not yet checked against valid enums here — that canonicalization is the sanitizer's job — but they must at least be real, non-empty strings.
Source
Thrown at skills/last30days/scripts/lib/planner.py:176
"jobs": {"jobs", "company_signal", "link"},
"corpus": {"reference", "analysis"},
}
def validate_external_plan(raw: dict) -> None:
"""Validate explicit-plan structure before permissive sanitization.
Enum-like metadata stays permissive because direct pipeline callers rely on
the sanitizer to canonicalize those values.
"""
if not isinstance(raw, dict):
raise ValueError("top-level plan must be an object")
for field in ("intent", "freshness_mode", "cluster_mode", "subqueries"):
if field not in raw:
raise ValueError(f"missing required field '{field}'")
for field in ("intent", "freshness_mode", "cluster_mode"):
if not isinstance(raw[field], str) or not raw[field].strip():
raise ValueError(f"field '{field}' must be a non-empty string")
source_weights = raw.get("source_weights")
if source_weights is not None and not isinstance(source_weights, dict):
raise ValueError("field 'source_weights' must be an object when provided")
for source, weight in (source_weights or {}).items():
if (
not isinstance(source, str)
or not source.strip()
or isinstance(weight, bool)
or not isinstance(weight, (int, float))
):
raise ValueError("field 'source_weights' must map source names to numbers")
subqueries = raw["subqueries"]
if not isinstance(subqueries, list) or not subqueries:
raise ValueError("field 'subqueries' must be a non-empty array")
for index, subquery in enumerate(subqueries):
if not isinstance(subquery, dict):
raise ValueError(f"subqueries[{index}] must be an object")View on GitHub (pinned to c7460f6114)
Solutions
- Set each of intent/freshness_mode/cluster_mode to a real non-empty string (e.g. "factual", "recent", "thematic").
- If a value came back as null from a generator, default it before submission.
- Strip values and assert truthiness in your plan builder.
Example fix
# before
{"intent": " ", "freshness_mode": None, "cluster_mode": "thematic", "subqueries": [...]}
# after
{"intent": "factual", "freshness_mode": "recent", "cluster_mode": "thematic", "subqueries": [...]} Defensive patterns
Strategy: type-guard
Validate before calling
for f in ("intent", "freshness_mode", "cluster_mode"):
v = plan.get(f)
if not isinstance(v, str) or not v.strip():
plan[f] = DEFAULTS[f] # or raise before the pipeline does Type guard
def is_nonempty_str(v) -> bool:
return isinstance(v, str) and bool(v.strip()) Try / catch
try:
planner.validate_external_plan(plan)
except ValueError as exc:
if "must be a non-empty string" in str(exc):
raise PlanInputError(f"metadata fields must be strings: {exc}") from exc
raise Prevention
- Coerce/null-check the three metadata fields in your plan emitter.
- Never pass null for a mode — pick a concrete value like "factual"/"recent"/"thematic".
When it happens
Trigger: Passing intent: 123 or intent: null; passing " " (whitespace-only); passing an empty string "" for any of the three metadata fields.
Common situations: LLM-generated plans emitting null for optional-looking fields; templates with placeholder spaces; numbers or booleans where a string mode was expected (e.g. freshness_mode: 7 instead of "recent").
Related errors
- top-level plan must be an object
- missing required field '{field}'
- field 'source_weights' must be an object when provided
- field 'source_weights' must map source names to numbers
- field 'subqueries' must be a non-empty array
AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15).
Data as JSON: /api/errors/68f2649f21c85de5.
Report an issue: GitHub.