mvanhorn/last30days-skill · error · RuntimeError

{role} must use a Gemini 3.1 model. Got: {model}

Error message

{role} must use a Gemini 3.1 model. Got: {model}

What it means

RuntimeError from _require_gemini_31 enforcing that planner and rerank model pins start with 'gemini-3.1-' when the Gemini provider is selected. It is a hard pin: older gemini-1.5/2.x model overrides are rejected at runtime resolution time, not silently downgraded.

Source

Thrown at skills/last30days/scripts/lib/providers.py:330

        return runtime, OpenRouterClient(openrouter_key)

    raise RuntimeError(f"Unsupported reasoning provider: {provider_name}")


def _resolve_x_backend(config: dict[str, Any]) -> str | None:
    """Resolve the X backend for runtime fetch.

    Delegates to env.get_x_source which handles:
    - Any known pin (X_BACKEND_KNOWN) exclusively: returns pin if available, None otherwise
    - Unpinned: walks auto-chain (X_BACKEND_ORDER) only, never auto-selects opt-in backends
    """
    return env.get_x_source(config)


def _require_gemini_31(model: str, *, role: str) -> None:
    if model.startswith("gemini-3.1-"):
        return
    raise RuntimeError(
        f"{role} must use a Gemini 3.1 model. Got: {model}"
    )


def extract_json(text: str) -> dict[str, Any]:
    """Extract the first JSON object from a model response."""
    text = text.strip()
    if not text:
        raise ValueError("Expected JSON response, got empty text")
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        match = re.search(r"\{[\s\S]*\}", text)
        if not match:
            raise
        return json.loads(match.group(0))

View on GitHub (pinned to c7460f6114)

Solutions

  1. Update model overrides to gemini-3.1-* variants (e.g. gemini-3.1-pro)
  2. Unset custom model pins so the built-in defaults (already 3.1) apply
  3. If you genuinely must run older models, change the pin policy in _require_gemini_31 consciously - do not bypass the check

Example fix

# before
LAST30DAYS_PLANNER_MODEL=gemini-2.5-pro

# after
LAST30DAYS_PLANNER_MODEL=gemini-3.1-pro
Defensive patterns

Strategy: validation

Validate before calling

for var in ("LAST30DAYS_PLANNER_MODEL", "LAST30DAYS_RERANK_MODEL"):
    model = os.environ.get(var, "")
    if model and not model.startswith("gemini-3.1-"):
        raise SystemExit(f"{var}={model!r} must start with 'gemini-3.1-'")

Type guard

def is_gemini_31(model: str) -> bool:
    return model.startswith("gemini-3.1-")

Try / catch

try:
    runtime, client = resolve_runtime(config, depth)
except RuntimeError as e:
    if "must use a Gemini 3.1 model" in str(e):
        config.pop("LAST30DAYS_PLANNER_MODEL", None)
        config.pop("LAST30DAYS_RERANK_MODEL", None)
        runtime, client = resolve_runtime(config, depth)  # defaults satisfy the pin

Prevention

When it happens

Trigger: Provider gemini with LAST30DAYS_PLANNER_MODEL / LAST30DAYS_RERANK_MODEL (or depth-based defaults) resolving to anything not prefixed 'gemini-3.1-', e.g. 'gemini-2.5-pro' or a typo'd pin. Both roles are checked before the runtime is constructed.

Common situations: Carry-over env overrides from before the 3.1 pin; docs/blog examples citing older model ids; users pinning flash/lite variants with wrong version strings; forks relaxing the pin in one call site but not this helper.

Related errors


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