gastownhall/beads · error

workspacegate: resolving %s: %w

Error message

workspacegate: resolving %s: %w

What it means

workspacegate.forDir fails to resolve the given directory to an absolute path, wrapping the underlying filepath.Abs error as "workspacegate: resolving %s: %w". This happens before the gate file identity is computed, so no lock was taken. It is an infrastructure-level resolution failure, not a gate-conflict error.

Source

Thrown at internal/workspacegate/gate.go:165

// gateFileName maps a guarded directory base name to its sibling gate
// file name: ".beads" -> ".beads.gate.lock", "dolt" -> "dolt.gate.lock".
// The base is kept verbatim (no dot-stripping) so distinct sibling names
// can never collide on one gate file.
func gateFileName(base string) string {
	return base + ".gate.lock"
}

// forDir builds the gate for a guarded directory: the gate file sits in
// the directory's parent. The parent must exist; the guarded directory
// itself may not exist yet (bd init gates the workspace it is creating).
// The parent is canonicalized (symlinks resolved) so that every process
// that reaches the same physical parent agrees on one gate file, no
// matter which path spelling it used.
func forDir(dir string) (Gate, error) {
	abs, err := filepath.Abs(dir)
	if err != nil {
		return Gate{}, fmt.Errorf("workspacegate: resolving %s: %w", dir, err)
	}
	abs = filepath.Clean(abs)

	// Gate identity must be stable across the guarded directory being
	// absent, created, replaced, or recreated — that is the point of
	// placing the gate beside it. So identity derives from the
	// canonicalized PARENT plus the literal base name, never from
	// resolving the guarded path itself: full-path resolution would
	// silently select a different gate once the directory appears as a
	// symlink, letting two exclusive holders coexist. The flip side is
	// that a guarded directory that IS a symlink has no stable identity
	// under this scheme, so it is refused outright rather than gated
	// ambiguously.
	if fi, err := os.Lstat(abs); err == nil && fi.Mode()&os.ModeSymlink != 0 {
		return Gate{}, fmt.Errorf("workspacegate: %s is a symlink; gate the physical directory it points to", abs)
	}
	parent, base := filepath.Split(abs)
	switch base {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Re-run the command from an existing directory (cd to a valid path first)
  2. Pass an absolute path to ForWorkspace/ForPhysicalRoot so filepath.Abs does not need the cwd
  3. Inspect the wrapped error (%w) for the OS-specific cause (e.g. ENOENT, ENAMETOOLONG) and fix the path accordingly

Example fix

// before
g, err := workspacegate.ForWorkspace(relDir)
// after
abs, err := filepath.Abs(relDir)
if err != nil { return fmt.Errorf("bad workdir %q: %w", relDir, err) }
g, err := workspacegate.ForWorkspace(abs)
Defensive patterns

Strategy: try-catch

Validate before calling

if abs, err := filepath.Abs(dir); err != nil { return fmt.Errorf("cannot resolve %q: %w", dir, err) }

Try / catch

g, err := workspacegate.ForWorkspace(dir)
if err != nil && strings.HasPrefix(err.Error(), "workspacegate: resolving ") { /* cwd/path issue: cd to a valid dir or use absolute path */ }

Prevention

When it happens

Trigger: Calling ForWorkspace(dir) or ForPhysicalRoot(dir) with a path that cannot be made absolute — practically this occurs when the current working directory is unavailable (e.g. the directory was deleted) and dir is relative, or on OS-level path resolution failures.

Common situations: Running bd from a directory that was removed or renamed by another process; containers or shells with a deleted cwd; extremely long or invalid path components on some filesystems.

Related errors


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