shareAI-lab/learn-claude-code · error · GoalError

goal evaluator 'impossible' must be boolean

Error message

goal evaluator 'impossible' must be boolean

What it means

The optional 'impossible' field defaults to false but, when present, must be a real boolean; GoalError("goal evaluator 'impossible' must be boolean") guards against truthy stand-ins like 1, "yes", or null. impossible=true lets the loop terminate a goal that cannot be met, so its type must be exact.

Source

Thrown at s17_goal_loop/code.py:192

        lines = stripped.splitlines()
        if lines and lines[0].startswith("```"):
            lines = lines[1:]
        if lines and lines[-1].strip() == "```":
            lines = lines[:-1]
        stripped = "\n".join(lines).strip()
    try:
        value = json.loads(stripped)
    except json.JSONDecodeError as error:
        raise GoalError("goal evaluator returned invalid JSON") from error
    if not isinstance(value, dict):
        raise GoalError("goal evaluator must return a JSON object")
    if not isinstance(value.get("ok"), bool):
        raise GoalError("goal evaluator response requires boolean 'ok'")
    if not isinstance(value.get("reason"), str) or not value["reason"].strip():
        raise GoalError("goal evaluator response requires non-empty 'reason'")
    impossible = value.get("impossible", False)
    if not isinstance(impossible, bool):
        raise GoalError("goal evaluator 'impossible' must be boolean")
    if value["ok"] and impossible:
        raise GoalError(
            "goal evaluator cannot return both ok and impossible"
        )
    return {
        "ok": value["ok"],
        "reason": value["reason"].strip(),
        "impossible": impossible,
    }


class PromptGoalEvaluator:
    """A separate, tool-free model that judges the transcript."""

    def __init__(
        self,
        client: Any,
        model: str,

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Constrain 'impossible' in the evaluator schema to type:boolean (or omit the field entirely when false).
  2. Show the allowed literal values in the evaluator prompt example.
  3. Coerce in a custom evaluator: emit the field only as a JSON true/false literal.

Example fix

# before: {"ok": false, "reason": "blocked", "impossible": "no"}

# after: {"ok": false, "reason": "blocked", "impossible": false}
Defensive patterns

Strategy: type-guard

Validate before calling

import json
obj = json.loads(evaluator_text)
imp = obj.get("impossible", False)
assert imp is True or imp is False, "'impossible' must be a JSON boolean when present"

Type guard

def impossible_is_bool_or_absent(value: object) -> bool:
    if not isinstance(value, dict) or "impossible" not in value:
        return True
    return isinstance(value["impossible"], bool)

Try / catch

try:
    ev = _parse_json_object(text)
except GoalError as e:
    if "'impossible' must be boolean" in str(e):
        obj = json.loads(text)
        obj.pop("impossible", None)  # drop malformed optional field, default false applies
        ev = _parse_json_object(json.dumps(obj))
    else:
        raise

Prevention

When it happens

Trigger: Evaluator JSON containing "impossible": "no" / 0 / null / [false] — present but not a bool.

Common situations: Models echoing free-text yes/no values; schemas allowing any type for the field; evaluators copying 'impossible' from prose annotations.

Related errors


AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14). Data as JSON: /api/errors/27ed672ab6671cbb. Report an issue: GitHub.