gastownhall/beads · info

lock already held by another process

Error message

lock already held by another process

What it means

On WASM, errProcessLocked is declared for API parity with the other platforms, but flockExclusive is a no-op that always returns nil — WASM environments are typically single-process and file locking is unsupported. The sentinel can therefore only be matched, not normally produced, on this build target.

Source

Thrown at internal/lockfile/lock_wasm.go:10

//go:build js && wasm

package lockfile

import (
	"errors"
	"os"
)

var errProcessLocked = errors.New("lock already held by another process")

func flockExclusive(f *os.File) error {
	// WASM doesn't support file locking
	// In a WASM environment, we're typically single-process anyway
	return nil // No-op in WASM
}

// FlockExclusiveNonBlocking attempts to acquire an exclusive lock without blocking.
// In WASM, this is a no-op since we're single-process.
func FlockExclusiveNonBlocking(f *os.File) error {
	return nil
}

// FlockExclusiveBlocking acquires an exclusive blocking lock on the file.
// In WASM, this is a no-op since we're single-process.
func FlockExclusiveBlocking(f *os.File) error {
	return nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Do not rely on real mutual exclusion in WASM builds; use a JS-side or application-level lock if cross-process exclusion is required
  2. Keep using lockfile.IsLocked/ErrLockBusy in shared code so behavior stays portable across platforms
  3. Document that WASM locks are advisory no-ops to avoid false confidence in exclusivity

Example fix

// before
// assumes Acquire excludes other processes on every platform
err := lockfile.Acquire(path)
// after
err := lockfile.Acquire(path)
if runtime.GOOS == "js" {
    // locking is a no-op on WASM; enforce exclusion at the app level
}
Defensive patterns

Strategy: fallback

Validate before calling

if runtime.GOOS == "js" {
    // lockfile is a no-op on wasm; use a JS-side mutex for cross-tab exclusion
}

Try / catch

err := lockfile.Acquire(path)
if err != nil && lockfile.IsLocked(err) {
    return fallbackApplicationLock() // wasm locks are advisory no-ops
}

Prevention

When it happens

Trigger: Code paths referencing errProcessLocked/lockfile.IsLocked under GOOS=js/wasm compile and behave the same; in practice the lock functions succeed unconditionally, so this error is essentially unreachable on WASM unless surfaced by shared code that checks it.

Common situations: Cross-compiling bd to WASM (browser/Node) where unix and windows lock implementations are swapped out; tests built for WASM asserting lock behavior that does not apply on that platform.

Related errors


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