calesthio/OpenMontage · error · TimeoutError
ClipCache: could not acquire lock at {self.lock_path} after
Error message
ClipCache: could not acquire lock at {self.lock_path} after {timeout}s What it means
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.
Source
Thrown at tools/video/clip_cache.py:245
yield
return
# Fallback: O_EXCL create-file lock.
deadline = time.time() + timeout
acquired = False
while time.time() < deadline:
try:
fd = os.open(
str(self.lock_path),
os.O_CREAT | os.O_EXCL | os.O_WRONLY,
)
os.close(fd)
acquired = True
break
except FileExistsError:
time.sleep(0.05)
if not acquired:
raise TimeoutError(
f"ClipCache: could not acquire lock at {self.lock_path} "
f"after {timeout}s"
)
try:
yield
finally:
try:
os.unlink(self.lock_path)
except OSError:
pass
# ------------------------------------------------------------------
# Manifest I/O (caller holds the lock)
# ------------------------------------------------------------------
def _read_manifest(self) -> dict[str, CacheEntry]:
"""Read the manifest file into a dict keyed by clip_id.
View on GitHub (pinned to 95e1c3d0ab)
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
Example fix
# before (two shells) $ om clip build & $ om clip search 'ocean' # second times out # after $ flock cache.lock -c 'om clip build' && om clip search 'ocean' # or remove stale lock: rm <lock_path from error message>, then retry
Defensive patterns
Strategy: retry
Validate before calling
import os, time
lock = Path(cache.lock_path)
if lock.exists():
age = time.time() - lock.stat().st_mtime
holder_alive = any('om' in (p.info or '') for p in psutil.Process().children(recursive=True)) # or pgrep your writer
if age > 300 and not holder_alive:
lock.unlink() # stale lock cleanup before calling the tool Try / catch
for attempt in range(3):
try:
with clip_cache.locked(timeout=30):
clip_cache.write(records)
break
except TimeoutError:
if attempt == 2:
raise
time.sleep(2 ** attempt) Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Checkpoint artifacts must be a dictionary
- Backlot server did not become healthy
- Prompt {prompt_id} did not complete within {timeout}s. The j
- Prompt {prompt_id} did not complete within {timeout}s (webso
- Kling Classic task {task_id} timed out after {timeout_second
AI-assisted analysis of calesthio/OpenMontage@95e1c3d0ab (2026-08-15).
Data as JSON: /api/errors/c94475c6b46527fc.
Report an issue: GitHub.