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

meta must be an object literal

Error message

meta must be an object literal

What it means

validate_meta requires the workflow metadata argument to be a Python dict (object literal) before launch. Anything else — a JSON string, a list, None — raises WorkflowInputError immediately, because every subsequent check (name, description, phases) indexes into it.

Source

Thrown at s16_workflow_runtime/code.py:123

            ) from exc
        yield
    finally:
        if handle is not None:
            try:
                fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
            finally:
                handle.close()
        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."""

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Parse before passing: json.loads(meta) / yaml.safe_load(text) and pass the resulting dict
  2. If using a dataclass, convert with dataclasses.asdict() at the call site
  3. Add a boundary check asserting isinstance(meta, dict) right after deserialization

Example fix

# before
meta = Path("workflow.json").read_text()  # str
run(meta=meta, ...)

# after
meta = json.loads(Path("workflow.json").read_text())
run(meta=meta, ...)
Defensive patterns

Strategy: type-guard

Validate before calling

def is_meta_object(meta) -> bool:
    return isinstance(meta, dict)

if isinstance(meta, str):
    meta = json.loads(meta)  # or yaml.safe_load for YAML
assert is_meta_object(meta)

Type guard

def is_workflow_meta(meta) -> bool:
    """Narrow to a dict suitable for validate_meta."""
    return isinstance(meta, dict)

Try / catch

try:
    validate_meta(meta)
except WorkflowInputError as exc:
    if "object literal" in str(exc):
        meta = json.loads(meta) if isinstance(meta, str) else dict(meta)
        validate_meta(meta)
    else:
        raise

Prevention

When it happens

Trigger: Calling run(json.dumps(meta), ...) instead of run(meta, ...). Loading meta from a YAML/JSON file and forgetting json.loads/yaml.safe_load. Passing a dataclass or namedtuple that quacks like meta but is not a dict.

Common situations: Config read from disk stays a string. APIs that forward a request body as text. Refactors that introduce a Meta dataclass without converting it at the boundary.

Related errors


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