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

workflow run 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.

Solutions

  1. Let the first invocation finish before retrying, or make retries conditional on the run no longer being active
  2. Generate a fresh run_id per logical execution so concurrent runs are distinct runs
  3. 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

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


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

Appendix: 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)