HKUDS/Vibe-Trading · error · TypeError

'playbook_slug' must be a string or null

Error message

'playbook_slug' must be a string or null

What it means

Raised by Job from_dict (agent/src/scheduled_research/models.py:482) when 'playbook_slug' is present but is neither a string nor null. The slug references a playbook file for playbook-sourced jobs; non-string values (int, list, dict) are rejected. null is valid and expected for prompt-sourced jobs.

Source

Thrown at agent/src/scheduled_research/models.py:482

        tz = raw_tz if isinstance(raw_tz, str) and raw_tz.strip() else None
        if raw_tz is not None and tz is None:
            logger.warning(
                "scheduled research job %s has an unusable timezone %r; "
                "evaluating its schedule in UTC",
                job_id,
                raw_tz,
            )
        status = JobStatus(data["status"])
        title = data.get("title", "")
        source_type = data.get("source_type", "prompt")
        playbook_slug = data.get("playbook_slug")
        end_at = data.get("end_at")
        if not isinstance(title, str):
            raise TypeError("'title' must be a string")
        if source_type not in {"prompt", "playbook"}:
            raise ValueError("'source_type' must be 'prompt' or 'playbook'")
        if playbook_slug is not None and not isinstance(playbook_slug, str):
            raise TypeError("'playbook_slug' must be a string or null")
        if end_at is not None and (isinstance(end_at, bool) or not isinstance(end_at, int)):
            raise TypeError("'end_at' must be an integer (epoch ms) or null")
        raw_config = data.get("config")
        config: Dict[str, Any] = raw_config if isinstance(raw_config, dict) else {}
        delivery_channel = data.get("delivery_channel")
        delivery_target = data.get("delivery_target")
        delivery_target_ref = data.get("delivery_target_ref")
        delivery_target_label = data.get("delivery_target_label")
        for name, value in (
            ("delivery_channel", delivery_channel),
            ("delivery_target", delivery_target),
            ("delivery_target_ref", delivery_target_ref),
            ("delivery_target_label", delivery_target_label),
        ):
            if value is not None and not isinstance(value, str):
                raise TypeError(f"'{name}' must be a string or null")
        return cls(
            id=job_id,

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Keep slugs as strings: str(slug)
  2. Omit or null the field for prompt-sourced jobs
  3. Validate slug format at your input boundary

Example fix

// before
record["playbook_slug"] = 42

// after
record["playbook_slug"] = "42"  # or the actual slug string
Defensive patterns

Strategy: validation

Validate before calling

def playbook_slug_ok(d):
    v = d.get("playbook_slug")
    return v is None or isinstance(v, str)

Type guard

def safe_slug(v):
    return v if (v is None or isinstance(v, str)) else str(v)

Try / catch

try:
    job = Job.from_dict(record)
except TypeError as exc:
    if "playbook_slug" in str(exc):
        record = dict(record); record["playbook_slug"] = None
        job = Job.from_dict(record)
    else:
        raise

Prevention

When it happens

Trigger: {"playbook_slug": 42} or {"playbook_slug": ["daily"]} on a playbook job record; absent or null passes.

Common situations: Slugs stored as identifiers with numeric prefixes coerced to int; building records from untyped API payloads.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/54cc8dfc3330b354. Report an issue: GitHub.