shareAI-lab/learn-claude-code · error · WorkflowInputError
invalid workflow runId
Error message
invalid workflow runId
What it means
validate_run_id enforces the canonical shape ^wf_[A-Za-z0-9][A-Za-z0-9._-]{0,63}_[0-9a-f]{16}$ — the 'wf_' prefix, a slug body of at most 64 characters, and a 16-lowercase-hex suffix. Any run_id that is not a string, or deviates from that shape, raises WorkflowInputError. Internally it also acts as a backstop on create_run_id output: because the slug portion shares the 64-character budget with the fixed affixes, a workflow name longer than 46 characters makes even a freshly generated id invalid.
Source
Thrown at s16_workflow_runtime/code.py:75
for _ in range(32):
run_id = validate_run_id(create_run_id(meta))
snapshot_path = STORE / f"{run_id}.json"
try:
fd = os.open(snapshot_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
except FileExistsError:
continue
os.close(fd)
return run_id
raise WorkflowInputError("could not allocate a unique workflow runId")
def create_task_id(run_id) -> str:
return f"local_workflow_{run_id}"
def validate_run_id(run_id):
if not isinstance(run_id, str) or not RUN_ID_RE.fullmatch(run_id):
raise WorkflowInputError("invalid workflow runId")
return run_id
# -- Errors --
class WorkflowInputError(Exception):
"""Bad workflow, metadata, or schema input."""
_run_locks_guard = threading.Lock()
_run_locks: dict[str, threading.Lock] = {}
@contextmanager
def workflow_run_lock(run_id: str):
"""Hold one run across threads and host processes for its full lifecycle."""
with _run_locks_guard:
local_lock = _run_locks.setdefault(run_id, threading.Lock())
if not local_lock.acquire(blocking=False):View on GitHub (pinned to 985456f4ad)
Solutions
- Only use ids returned by reserve_run_id/run; never hand-build them
- Keep workflow meta.name to 46 characters or fewer so generated ids always validate
- If resuming, copy the id verbatim (lowercase hex, full 16 chars) from the store listing
Example fix
# before
run_id = f"wf_{workflow_name}_{uuid4().hex[:12]}" # wrong shape, wrong hex length
# after
run_id = reserve_run_id(meta) # wf_<slug>_<16 lowercase hex>, pre-validated Defensive patterns
Strategy: validation
Validate before calling
import re
RUN_ID_RE = re.compile(r"^wf_[A-Za-z0-9][A-Za-z0-9._-]{0,63}_[0-9a-f]{16}$")
def is_valid_run_id(run_id) -> bool:
return isinstance(run_id, str) and bool(RUN_ID_RE.fullmatch(run_id))
assert is_valid_run_id(run_id), f"malformed run id: {run_id!r}" Type guard
def is_valid_run_id(run_id) -> bool:
import re
return isinstance(run_id, str) and bool(re.fullmatch(r"wf_[A-Za-z0-9][A-Za-z0-9._-]{0,63}_[0-9a-f]{16}", run_id)) Try / catch
try:
validate_run_id(run_id)
except WorkflowInputError:
# do not hand-repair ids; list the store and pick a real one, or reserve a new one
run_id = reserve_run_id(meta) Prevention
- Treat run ids as opaque tokens produced by reserve_run_id
- Keep meta.name <= 46 chars so generated ids always fit the pattern
- Persist ids unedited (no trimming, no uppercase)
When it happens
Trigger: Passing a user-supplied or hand-built run_id like 'my-run' or 'WF_docs_abc123' to resume(). Copying a run id with the hex suffix uppercased or truncated. Naming a workflow with 47+ characters, since wf_ + name + _ + 16 hex exceeds the pattern.
Common situations: Scripts that store run ids in configs and resume them later, with edits/corruption in transit. Teams assuming the 64-char WORKFLOW_NAME_RE budget applies fully to run ids. Manually constructing ids instead of using reserve_run_id.
Related errors
- meta.name must be a 1-64 character slug using letters, numbe
- resume runId does not match workflow meta
- Invalid mailbox recipient: {agent!r}
- MCP names cannot normalize to an empty string
- MCP tool name is longer than 64 characters: {prefixed}
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/9074e6111bac33e6.
Report an issue: GitHub.