shareAI-lab/learn-claude-code · error · WorkflowInputError
workflow run {run_id} is already active
Error message
workflow run {run_id} is already active What it means
workflow_run_lock first takes an in-process threading.Lock keyed by run_id; a non-blocking acquire failure means another thread in this process is already running that run, and WorkflowInputError is raised immediately. This is the cheap first tier of a two-tier lock that also uses an flock file (see error 108) to guard across processes.
Source
Thrown at s16_workflow_runtime/code.py:94
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):
raise WorkflowInputError(f"workflow run {run_id} is already active")
handle = None
try:
STORE.mkdir(parents=True, exist_ok=True)
handle = (STORE / f"{run_id}.lock").open("a+")
try:
fcntl.flock(handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError as exc:
raise WorkflowInputError(
f"workflow run {run_id} is already active"
) from exc
yield
finally:
if handle is not None:
try:
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
finally:
handle.close()View on GitHub (pinned to 985456f4ad)
Solutions
- Let the first invocation finish before retrying, or make retries conditional on the run no longer being active
- Generate a fresh run_id per logical execution so concurrent runs are distinct runs
- Catch WorkflowInputError around launch and surface 'already running' to the caller instead of failing opaquely
Example fix
# before run_async(workflow, run_id=fixed_id) # retry while first still active # after run_id = reserve_run_id(meta) # unique per execution run_async(workflow, run_id=run_id)
Defensive patterns
Strategy: try-catch
Validate before calling
# Best pre-check: make concurrent runs distinct by construction run_id = reserve_run_id(meta) # unique per execution; same-thread re-entry then cannot collide
Try / catch
try:
with workflow_run_lock(run_id):
result = run(workflow, run_id=run_id)
except WorkflowInputError as exc:
if "already active" in str(exc):
# another thread in this process holds it; poll for completion or return 409-style status
return {"status": "already-running", "run_id": run_id}
raise Prevention
- One run_id per logical execution via reserve_run_id
- Make API retries idempotent on run_id, not blind re-invocations
- Guard scheduler and manual triggers behind the same run lock
When it happens
Trigger: Two threads calling run()/resume() with the same run_id concurrently — e.g. a web handler retrying a request while the first is still executing, or a scheduler firing a workflow that a manual invocation already started.
Common situations: Duplicate HTTP submissions racing each other. Retry middleware that re-invokes an async workflow before the first attempt finished. Background scheduler overlapping with a manual trigger.
Related errors
- Assignment for {owner} is no longer active
- Assignment for {owner} is no longer active
- Assignment cwd changed for task {task.id}
- could not allocate a unique workflow runId
- invalid workflow runId
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/a684ee3d808ee596.
Report an issue: GitHub.