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
- Wait for the concurrent baml process to finish, then retry the install.
- Delete the stale lock file shown in the message and re-run the command.
- Ensure CI runs installs with a per-job lock or serialized steps.
- 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
- Serialize baml installs in CI (don't run them in parallel jobs).
- Clean up lock files in signal/exit handlers so crashes don't leave stale locks.
- Monitor for orphaned lock files in shared/NFS mounts.
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.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- {selector} is a local path; there is nothing to install. Run
- {err}
- failed to replace {} with {}: {}
- failed to install {} into {}
- Timed out after {} minutes waiting for the login to be confi
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/55afa2c1ed5e5484.
Report an issue: GitHub.