gastownhall/beads · error

identity: resolve root path: %w

Error message

identity: resolve root path: %w

What it means

RootID resolves the workspace root directory to a stable ID by taking its absolute path, resolving symlinks with filepath.EvalSymlinks, and hashing the result with SHA-256. This error wraps any failure from EvalSymlinks, which fails if any component of the path does not exist or is an unreadable/broken symlink. The library throws it because it cannot compute a canonical path for a root that the filesystem does not resolve.

Source

Thrown at internal/storage/dbproxy/identity/identity.go:34

// SecretFileName is the per-workspace secret used to authenticate control
// listener requests.
const SecretFileName = "proxy.secret"

// RootID returns the SHA-256 of rootDir's symlink-resolved absolute path.
// It identifies the workspace proxy root, not the Dolt data directory;
// upstream_id continues to identify the backend through DoltServer.ID.
// Darwin's default case-insensitive filesystems can resolve the same directory
// through differently cased path spellings; callers should use a canonical
// workspace spelling when they need stable IDs across invocations.
func RootID(rootDir string) (string, error) {
	abs, err := filepath.Abs(rootDir)
	if err != nil {
		return "", fmt.Errorf("identity: absolute root path: %w", err)
	}
	resolved, err := filepath.EvalSymlinks(abs)
	if err != nil {
		return "", fmt.Errorf("identity: resolve root path: %w", err)
	}
	sum := sha256.Sum256([]byte(resolved))
	return hex.EncodeToString(sum[:]), nil
}

// WriteSecret creates and atomically writes a new control-listener secret.
// Each proxy start intentionally rotates the previous secret.
func WriteSecret(rootDir string) (string, error) {
	raw := make([]byte, 32)
	if _, err := rand.Read(raw); err != nil {
		return "", fmt.Errorf("identity: generate proxy secret: %w", err)
	}
	secret := hex.EncodeToString(raw)
	if err := atomicfile.WriteFile(filepath.Join(rootDir, SecretFileName), []byte(secret+"\n"), 0o600); err != nil {
		return "", fmt.Errorf("identity: write proxy secret: %w", err)
	}
	return secret, nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Create the root directory before calling RootID, e.g. os.MkdirAll(rootDir, 0o755)
  2. Verify the path exists and is traversable: os.Stat(filepath.EvalSymlinks'd absolute path) and check errors.Is(err, fs.ErrNotExist)
  3. Fix or remove broken symlinks in the path chain
  4. Correct the rootDir value (typo, stale env var) to the actual workspace root

Example fix

// before
id, err := identity.RootID(rootDir) // fails if rootDir missing
// after
if err := os.MkdirAll(rootDir, 0o755); err != nil {
    return err
}
id, err := identity.RootID(rootDir)
Defensive patterns

Strategy: validation

Validate before calling

if _, err := os.Stat(rootDir); err != nil {
    return fmt.Errorf("workspace root %q unavailable: %w", rootDir, err)
}
if _, err := filepath.EvalSymlinks(rootDir); err != nil {
    return fmt.Errorf("cannot resolve %q: %w", rootDir, err)
}

Try / catch

id, err := identity.RootID(rootDir)
if err != nil {
    var perr *fs.PathError
    if errors.As(err, &perr) && errors.Is(perr.Err, fs.ErrNotExist) {
        // create rootDir or surface a clear setup error
    }
    return err
}

Prevention

When it happens

Trigger: Calling identity.RootID(rootDir) when rootDir does not exist on disk, when a parent directory in the path is missing, when a symlink in the path is broken (dangling), or when the process lacks permission to traverse a path component (returns *fs.PathError, often os.ErrNotExist or os.ErrPermission).

Common situations: Starting the dbproxy against a workspace directory that was never created (typo in BEADS_DIR or --root flag), running after the workspace was deleted or moved, pointing at a symlink whose target was removed, or containers with a partially mounted volume.

Related errors


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