BoundaryML/baml · error · FetchError

timed out waiting for install lock {}

Error message

timed out waiting for install lock {}

What it means

acquire() implements a lockfile-based install mutex: it retries create_new() every second when the lock file already exists. If the lock is still held after the retry budget is exhausted, it returns an io::Error with ErrorKind::TimedOut formatted as "timed out waiting for install lock {path}". This means another process (or a stale one) holds the BAML install lock.

Source

Thrown at baml_language/crates/baml_release/src/lib.rs:254

    }
}

struct InstallLock {
    path: PathBuf,
}

impl InstallLock {
    fn acquire(path: PathBuf) -> Result<Self, FetchError> {
        for _ in 0..60 {
            match OpenOptions::new().write(true).create_new(true).open(&path) {
                Ok(_) => return Ok(Self { path }),
                Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
                    thread::sleep(Duration::from_secs(1));
                }
                Err(err) => return Err(FetchError::Io(err)),
            }
        }
        Err(FetchError::Io(std::io::Error::new(
            std::io::ErrorKind::TimedOut,
            format!("timed out waiting for install lock {}", path.display()),
        )))
    }
}

impl Drop for InstallLock {
    fn drop(&mut self) {
        let _ = fs::remove_file(&self.path);
    }
}

pub fn manifest_base_url() -> String {
    std::env::var("BAML_MANIFEST_BASE_URL")
        .ok()
        .filter(|value| !value.trim().is_empty())
        .unwrap_or_else(|| DEFAULT_MANIFEST_BASE_URL.to_string())
        .trim_end_matches('/')

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Wait for the concurrent baml process to finish, then retry the install.
  2. Delete the stale lock file shown in the message and re-run the command.
  3. Ensure CI runs installs with a per-job lock or serialized steps.
  4. If timeouts recur legitimately, increase the retry budget or remove the lock after checking no live process holds it.

Example fix

// before
baml install 0.200.0 &
baml install 0.200.0 &   # second one times out on the lock
// after
baml install 0.200.0
baml install 0.200.0     # serialize installs, or rm the stale lock first
Defensive patterns

Strategy: retry

Validate before calling

if lock_path.exists() {
    eprintln!("install lock exists: {} — waiting or removing stale lock", lock_path.display());
}

Try / catch

match acquire_install_lock() {
    Err(e) if e.kind() == std::io::ErrorKind::TimedOut => {
        if no_live_holder(&lock_path) { std::fs::remove_file(&lock_path)?; retry(); } else { backoff_and_retry(); }
    }
    r => r,
}

Prevention

When it happens

Trigger: Calling acquire (during install/self-update) while another baml process holds the lock file at `path`, or a previous crashed run left the lock file behind so create_new keeps failing with AlreadyExists until timeout.

Common situations: Two parallel `baml install`/CI jobs racing; a killed installer that never removed its lock; NFS/containers where stale locks persist; very slow installation exceeding the wait window.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/55afa2c1ed5e5484. Report an issue: GitHub.