mvanhorn/last30days-skill · error · ValueError

field 'source_weights' must be an object when provided

Error message

field 'source_weights' must be an object when provided

What it means

Optional-field shape check in validate_external_plan: source_weights may be omitted (None), but if present it must be a dict mapping source name → numeric weight. Passing a list of pairs, an array, or a string fails here before individual entries are examined.

Source

Thrown at skills/last30days/scripts/lib/planner.py:180

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")
        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")

View on GitHub (pinned to c7460f6114)

Solutions

  1. Use a JSON object: "source_weights": {"reddit": 2, "x": 1}.
  2. Convert array-of-pairs formats before submission: dict(pairs).
  3. Or omit source_weights entirely — it is optional.

Example fix

# before
"source_weights": [{"source": "reddit", "weight": 2}]

# after
"source_weights": {"reddit": 2}
Defensive patterns

Strategy: type-guard

Validate before calling

sw = plan.get("source_weights")
if sw is not None and not isinstance(sw, dict):
    plan["source_weights"] = dict(sw)  # only if it's an iterable of pairs; else drop

Type guard

def is_source_weights_map(v) -> bool:
    return v is None or (isinstance(v, dict) and all(isinstance(k, str) and k.strip() for k in v))

Prevention

When it happens

Trigger: external_plan includes "source_weights": [["reddit", 2], ["x", 1]] (array of pairs) or "source_weights": "reddit:2" — anything that is not None and not a dict.

Common situations: JSON authors modeling the map as an array of {source, weight} objects; YAML-to-JSON conversions changing the shape; LLM plans inventing a list format.

Related errors


AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15). Data as JSON: /api/errors/a6adc83d7dd516d5. Report an issue: GitHub.