{"record":{"id":"a684ee3d808ee596","repo":"shareAI-lab/learn-claude-code","slug":"workflow-run-run-id-is-already-active","errorCode":null,"errorMessage":"workflow run {run_id} is already active","messagePattern":"workflow run (.+?) is already active","errorType":"exception","errorClass":"WorkflowInputError","httpStatus":null,"severity":"error","filePath":"s16_workflow_runtime/code.py","lineNumber":94,"sourceCode":"    return run_id\n\n\n# -- Errors --\nclass WorkflowInputError(Exception):\n    \"\"\"Bad workflow, metadata, or schema input.\"\"\"\n\n\n_run_locks_guard = threading.Lock()\n_run_locks: dict[str, threading.Lock] = {}\n\n\n@contextmanager\ndef workflow_run_lock(run_id: str):\n    \"\"\"Hold one run across threads and host processes for its full lifecycle.\"\"\"\n    with _run_locks_guard:\n        local_lock = _run_locks.setdefault(run_id, threading.Lock())\n    if not local_lock.acquire(blocking=False):\n        raise WorkflowInputError(f\"workflow run {run_id} is already active\")\n\n    handle = None\n    try:\n        STORE.mkdir(parents=True, exist_ok=True)\n        handle = (STORE / f\"{run_id}.lock\").open(\"a+\")\n        try:\n            fcntl.flock(handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)\n        except BlockingIOError as exc:\n            raise WorkflowInputError(\n                f\"workflow run {run_id} is already active\"\n            ) from exc\n        yield\n    finally:\n        if handle is not None:\n            try:\n                fcntl.flock(handle.fileno(), fcntl.LOCK_UN)\n            finally:\n                handle.close()","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s16_workflow_runtime/code.py#L76-L112","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nrun_async(workflow, run_id=fixed_id)  # retry while first still active\n\n# after\nrun_id = reserve_run_id(meta)  # unique per execution\nrun_async(workflow, run_id=run_id)","handlingStrategy":"try-catch","validationCode":"# Best pre-check: make concurrent runs distinct by construction\nrun_id = reserve_run_id(meta)  # unique per execution; same-thread re-entry then cannot collide","typeGuard":null,"tryCatchPattern":"try:\n    with workflow_run_lock(run_id):\n        result = run(workflow, run_id=run_id)\nexcept WorkflowInputError as exc:\n    if \"already active\" in str(exc):\n        # another thread in this process holds it; poll for completion or return 409-style status\n        return {\"status\": \"already-running\", \"run_id\": run_id}\n    raise","preventionTips":["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"],"tags":["workflow","concurrency","locking","duplicate-run"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}