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

meta.description must be a string

Error message

meta.description must be a string

What it means

After the presence check, validate_meta requires meta['description'] specifically to be a str. Non-string truthy values — a number, list, or dict — pass the earlier truthiness gate but fail here, because the description is surfaced in UIs/logs and compared as text.

Source

Thrown at s16_workflow_runtime/code.py:131

        local_lock.release()
        with _run_locks_guard:
            if not local_lock.locked() and _run_locks.get(run_id) is local_lock:
                _run_locks.pop(run_id, None)


# -- Metadata Validation --
def validate_meta(meta):
    """Validate name, description, and optional phases before launch."""
    if not isinstance(meta, dict):
        raise WorkflowInputError("meta must be an object literal")
    if not meta.get("name") or not meta.get("description"):
        raise WorkflowInputError("meta requires `name` and `description`")
    if not isinstance(meta["name"], str) or not WORKFLOW_NAME_RE.fullmatch(meta["name"]):
        raise WorkflowInputError(
            "meta.name must be a 1-64 character slug using letters, numbers, '.', '_', or '-'"
        )
    if not isinstance(meta["description"], str):
        raise WorkflowInputError("meta.description must be a string")
    if "phases" in meta:
        if not isinstance(meta["phases"], list) or not all(
            isinstance(phase, str) and phase for phase in meta["phases"]
        ):
            raise WorkflowInputError("meta.phases must be a list of non-empty strings")
    return meta


def check_permission(meta, settings=None):
    """Apply the s03 allow/deny gate before launching a workflow."""
    settings = settings or {}
    if meta["name"] in settings.get("deny", []):
        raise WorkflowInputError(f"workflow '{meta['name']}' denied by settings")
    return "allow"


# -- Minimal JSON Schema --
class SimpleJsonSchema:

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Quote the description in YAML; ensure the value is a plain string
  2. Wrap computed descriptions with str(...) at the boundary
  3. For structured content, serialize to a string (e.g. json.dumps) first

Example fix

# before (YAML)
# description: 2024-01-30   <- parsed as a date object

# after
description: "Nightly ingest run for 2024-01-30"
Defensive patterns

Strategy: type-guard

Validate before calling

def description_is_str(meta: dict) -> bool:
    return isinstance(meta.get("description"), str) and bool(meta["description"])

assert description_is_str(meta)

Type guard

def meta_description_is_string(meta) -> bool:
    return isinstance(meta, dict) and isinstance(meta.get("description"), str)

Try / catch

try:
    validate_meta(meta)
except WorkflowInputError as exc:
    if "description must be a string" in str(exc):
        meta = {**meta, "description": str(meta["description"])}
        validate_meta(meta)
    else:
        raise

Prevention

When it happens

Trigger: run(meta={"name": "ingest", "description": 42}) or description set to a list of bullet points ["a", "b"]. YAML configs where an unquoted description parses as a number or boolean (yes/no).

Common situations: YAML implicit typing (description: 2024 becomes an int; description: yes becomes a bool). Programmatically inserting a computed value that happens to be non-text.

Related errors


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