gastownhall/beads · error

timeout after %s waiting for bootstrap lock (another bootstr

Error message

timeout after %s waiting for bootstrap lock (another bootstrap may be running)

What it means

The bootstrap lock is held by another process. acquireBootstrapLock polls with non-blocking flock every 100ms until the timeout deadline passes; if the lock is never released within that window, it gives up with this timeout error, closing its own file handle first. It indicates a concurrent bootstrap is running (or a stale holder).

Source

Thrown at internal/storage/dolt/bootstrap.go:250

	// Try to acquire lock with non-blocking flock and polling.
	deadline := time.Now().Add(timeout)
	for {
		err := lockfile.FlockExclusiveNonBlocking(f)
		if err == nil {
			// Lock acquired - update modification time for stale detection
			return f, nil
		}

		if !lockfile.IsLocked(err) {
			// Unexpected error (not contention)
			_ = f.Close() // Best effort cleanup on error path
			return nil, fmt.Errorf("failed to acquire bootstrap lock: %w", err)
		}

		if time.Now().After(deadline) {
			_ = f.Close() // Best effort cleanup on error path
			return nil, fmt.Errorf("timeout after %s waiting for bootstrap lock (another bootstrap may be running)", timeout)
		}

		// Wait briefly before retrying
		time.Sleep(100 * time.Millisecond)
	}
}

// releaseBootstrapLock releases the bootstrap lock and removes the lock file
func releaseBootstrapLock(f *os.File, lockPath string) {
	if f != nil {
		_ = lockfile.FlockUnlock(f) // Best effort: unlock may fail if fd is bad
		_ = f.Close()               // Best effort cleanup
	}
	// Clean up lock file
	_ = os.Remove(lockPath) // Best effort cleanup of lock file
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Wait for the other bootstrap to finish, then retry — the timeout message tells you another bootstrap may be running
  2. Find and stop the process holding the lock: `lsof .beads/bootstrap.lock` (or equivalent) and kill stale `bd`/`dolt` processes
  3. Increase spacing between concurrent agent sessions, or serialize bootstrap calls behind your own coordination
  4. If the holder is dead but the flock persists (unusual), delete the lock file and retry

Example fix

// before
bd bootstrap & bd bootstrap &  # second one times out
// after
bd bootstrap && bd doctor  # serialize; or wait for first to exit before retrying
Defensive patterns

Strategy: retry

Validate before calling

// detect a concurrent bootstrap before starting one
// Unix: fuser .beads/bootstrap.lock 2>/dev/null && echo 'bootstrap in progress'
// or check for running processes: pgrep -f 'bd bootstrap'

Try / catch

if err := bd.BootstrapFromRemoteWithDB(ctx, url, target); err != nil {
    if strings.Contains(err.Error(), "timeout after") {
        // wait/poll for the other bootstrap to finish, then retry;
        // if no holder exists, remove stale lock and retry
    }
}

Prevention

When it happens

Trigger: Two `bd bootstrap` invocations run concurrently on the same database (e.g. two agent sessions, a CI job overlapping local work), or a previous bootstrap process crashed/hung while still holding the flock on the lock file until the deadline expires.

Common situations: Parallel agent workflows each calling `bd bootstrap`; a long-running `bd` process (server mode) holding the lock; a stuck dolt subprocess from an earlier failed bootstrap.

Understand the failure class

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/b53b1671b9596488. Report an issue: GitHub.