mvanhorn/last30days-skill · error · ValueError
field 'source_weights' must map source names to numbers
Error message
field 'source_weights' must map source names to numbers
What it means
Per-entry check in validate_external_plan: every key in source_weights must be a non-empty string (after strip) and every value must be an int or float — and explicitly NOT a bool, since isinstance(True, int) is True in Python and booleans would otherwise sneak through as 1/0.
Source
Thrown at skills/last30days/scripts/lib/planner.py:188
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")
for field in ("search_query", "ranking_query"):
if not isinstance(subquery.get(field), str) or not subquery[field].strip():
raise ValueError(f"subqueries[{index}].{field} must be a non-empty string")
sources = subquery.get("sources")
if not isinstance(sources, list) or not sources or not all(
isinstance(source, str) and source.strip() for source in sources
):
raise ValueError(f"subqueries[{index}].sources must be a non-empty string array")
weight = subquery.get("weight")
if weight is not None and (
isinstance(weight, bool) or not isinstance(weight, (int, float))
):View on GitHub (pinned to c7460f6114)
Solutions
- Use plain numbers: {"reddit": 2, "hackernews": 1.5}.
- Convert string numbers with float(); drop null/boolean entries.
- Never use true/false as weights — remove the key entirely to disable a source.
Example fix
# before
"source_weights": {"reddit": true, "x": "3"}
# after
"source_weights": {"reddit": 2, "x": 3} Defensive patterns
Strategy: validation
Validate before calling
sw = plan.get("source_weights") or {}
plan["source_weights"] = {
k: float(v) for k, v in sw.items()
if isinstance(k, str) and k.strip() and isinstance(v, (int, float)) and not isinstance(v, bool)
} Type guard
def is_numeric_weight(v) -> bool:
return isinstance(v, (int, float)) and not isinstance(v, bool) Prevention
- Remember bool is a subclass of int in Python — JSON true/false will pass naive isinstance checks.
- Reject or cast string numbers with float() at the boundary.
When it happens
Trigger: source_weights containing a boolean value ({"reddit": true}), a string number ({"reddit": "2"}), null, a list, or an empty/whitespace key ({" ": 1}).
Common situations: JSON true/false leaking in from LLM plans meaning yes/no; weights quoted as strings; a null weight for a disabled source.
Related errors
- top-level plan must be an object
- missing required field '{field}'
- field '{field}' must be a non-empty string
- field 'source_weights' must be an object when provided
- field 'subqueries' must be a non-empty array
AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15).
Data as JSON: /api/errors/3c671192f00b2e5c.
Report an issue: GitHub.