{"record":{"id":"c94475c6b46527fc","repo":"calesthio/OpenMontage","slug":"clipcache-could-not-acquire-lock-at-self-lock-pa","errorCode":null,"errorMessage":"ClipCache: could not acquire lock at {self.lock_path} after {timeout}s","messagePattern":"ClipCache: could not acquire lock at (.+?) after (.+?)s","errorType":"exception","errorClass":"TimeoutError","httpStatus":null,"severity":"error","filePath":"tools/video/clip_cache.py","lineNumber":245,"sourceCode":"                yield\n            return\n\n        # Fallback: O_EXCL create-file lock.\n        deadline = time.time() + timeout\n        acquired = False\n        while time.time() < deadline:\n            try:\n                fd = os.open(\n                    str(self.lock_path),\n                    os.O_CREAT | os.O_EXCL | os.O_WRONLY,\n                )\n                os.close(fd)\n                acquired = True\n                break\n            except FileExistsError:\n                time.sleep(0.05)\n        if not acquired:\n            raise TimeoutError(\n                f\"ClipCache: could not acquire lock at {self.lock_path} \"\n                f\"after {timeout}s\"\n            )\n        try:\n            yield\n        finally:\n            try:\n                os.unlink(self.lock_path)\n            except OSError:\n                pass\n\n    # ------------------------------------------------------------------\n    # Manifest I/O (caller holds the lock)\n    # ------------------------------------------------------------------\n\n    def _read_manifest(self) -> dict[str, CacheEntry]:\n        \"\"\"Read the manifest file into a dict keyed by clip_id.\n","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/calesthio/OpenMontage/blob/95e1c3d0ab93482159818560f6a8c8e866b9139f/tools/video/clip_cache.py#L227-L263","documentation":"Raised by ClipCache's lock context manager when an O_CREAT|O_EXCL lockfile at self.lock_path could not be created within the timeout window (polling every 50ms). Some other process holds the cache lock — typically a long cache write, index rebuild, or a crashed process that left a stale lockfile behind.","triggerScenarios":"Two processes (e.g. two agent runs or a UI plus a CLI) hitting the same ClipCache concurrently and the first holding the lock longer than the timeout; a previous run killed mid-write leaving the lockfile on disk with no owner to remove it.","commonSituations":"Parallel cron jobs or CI shards sharing a cache directory; SIGKILL/power loss during a cache mutation; NFS or synced folders where unlink of the lock is delayed; extremely slow disks making a legitimate write exceed the timeout.","solutions":["Check for a live holder (ps/lsof on the lock's owner) — if none, delete the stale lockfile at the lock_path named in the message and retry","Serialize cache access across processes (single writer, or a queue) instead of racing","If concurrent access is legitimate, raise the timeout passed to the lock acquisition","Investigate why the holder takes >timeout: large corpus writes or slow storage"],"exampleFix":"# before (two shells)\n$ om clip build &   $ om clip search 'ocean'   # second times out\n\n# after\n$ flock cache.lock -c 'om clip build' && om clip search 'ocean'\n# or remove stale lock: rm <lock_path from error message>, then retry","handlingStrategy":"retry","validationCode":"import os, time\nlock = Path(cache.lock_path)\nif lock.exists():\n    age = time.time() - lock.stat().st_mtime\n    holder_alive = any('om' in (p.info or '') for p in psutil.Process().children(recursive=True))  # or pgrep your writer\n    if age > 300 and not holder_alive:\n        lock.unlink()  # stale lock cleanup before calling the tool","typeGuard":null,"tryCatchPattern":"for attempt in range(3):\n    try:\n        with clip_cache.locked(timeout=30):\n            clip_cache.write(records)\n        break\n    except TimeoutError:\n        if attempt == 2:\n            raise\n        time.sleep(2 ** attempt)","preventionTips":["Wrap cache-mutating commands in flock or a job queue so only one writer runs","Remove stale lockfiles after crashes (age-based heuristic: older than any plausible write)","Keep cache writes small and idempotent so lock hold time stays under the timeout"],"tags":["clip-cache","concurrency","file-lock","timeout"],"backgroundTag":null,"analyzedSha":"95e1c3d0ab93482159818560f6a8c8e866b9139f","analyzedAt":"2026-08-15T06:31:20.014Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}