gastownhall/beads · error

creating beads directory: %w

Error message

creating beads directory: %w

What it means

AcquireSyncLock ensures the beads directory exists before creating the lock file, calling os.MkdirAll(beadsDir, 0755). If directory creation fails it wraps the OS error as "creating beads directory: %w" and returns no lock. This typically means a filesystem-level problem, since MkdirAll succeeds silently when the directory already exists.

Source

Thrown at internal/linear/synclock.go:39

	file     *os.File
	metadata syncLockMetadata
}

// SyncLockInfo contains advisory metadata published while the sync lock is held.
type SyncLockInfo struct {
	PID     int
	Started time.Time
}

// AcquireSyncLock acquires the sync lock for the given beads directory.
// If wait is true, blocks until the lock is available. If false, returns
// an error immediately when the lock is held by another live process.
func AcquireSyncLock(beadsDir string, wait bool) (*SyncLock, error) {
	lockPath := filepath.Join(beadsDir, syncLockFilename)
	infoPath := syncLockMetadataPath(beadsDir, lockPath)

	if err := os.MkdirAll(beadsDir, 0755); err != nil {
		return nil, fmt.Errorf("creating beads directory: %w", err)
	}

	f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600) // #nosec G304 -- lockPath is constrained to the beads directory.
	if err != nil {
		return nil, fmt.Errorf("opening lock file: %w", err)
	}

	if wait {
		if err := lockfile.FlockExclusiveBlocking(f); err != nil {
			_ = f.Close()
			return nil, fmt.Errorf("acquiring lock (blocking): %w", err)
		}
	} else {
		if err := lockfile.FlockExclusiveNonBlocking(f); err != nil {
			if lockfile.IsLocked(err) || err == lockfile.ErrLockBusy {
				info := readContendedSyncLockInfo(infoPath)
				_ = f.Close()
				return nil, &SyncLockHeldError{Info: info}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check that no regular file exists at the beadsDir path (or along it); rename or remove it
  2. Verify write permission on the parent directory for the running user
  3. Confirm the filesystem is writable (not mounted read-only, disk not full)
  4. Validate the beadsDir string for typos or invalid characters before calling AcquireSyncLock

Example fix

// before: assume dir creatable
lock, err := AcquireSyncLock(beadsDir, true)
// after: pre-flight
if st, ferr := os.Stat(beadsDir); ferr == nil && !st.IsDir() {
    return fmt.Errorf("%s exists and is not a directory", beadsDir)
}
Defensive patterns

Strategy: validation

Validate before calling

if st, err := os.Stat(beadsDir); err == nil && !st.IsDir() {
    return fmt.Errorf("%s exists and is not a directory", beadsDir)
}
lock, err := AcquireSyncLock(beadsDir, true)

Type guard

null

Try / catch

lock, err := AcquireSyncLock(beadsDir, true)
if err != nil && strings.Contains(err.Error(), "creating beads directory") {
    return fmt.Errorf("fix filesystem access to %s: %w", beadsDir, err)
}

Prevention

When it happens

Trigger: os.MkdirAll(beadsDir, 0755) returns an error: parent path component is a file, permission denied on the parent, invalid path characters, or the filesystem is read-only.

Common situations: beadsDir points inside a read-only mount or container layer; a file exists where a directory is expected (e.g. .beads is a file); wrong user/permissions in multi-user setups; USB/network volume disconnected.

Related errors


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