gastownhall/beads · error

expected relative path, got absolute: %s

Error message

expected relative path, got absolute: %s

What it means

safeWorkspacePath rejects relPath values that are absolute paths. The function's contract is to join a workspace-relative path onto an absolute root; passing an absolute path would make the root irrelevant and bypass traversal containment, so it is refused with this error.

Source

Thrown at cmd/bd/doctor/fix/common.go:111

func localWorkspaceBeadsDir(path string) (string, error) {
	absPath, err := filepath.Abs(path)
	if err != nil {
		return "", fmt.Errorf("invalid path: %w", err)
	}
	return filepath.Join(absPath, ".beads"), nil
}

// safeWorkspacePath resolves relPath within the workspace root and ensures it
// cannot escape the workspace via path traversal.
func safeWorkspacePath(root, relPath string) (string, error) {
	absRoot, err := filepath.Abs(root)
	if err != nil {
		return "", fmt.Errorf("invalid workspace path: %w", err)
	}

	cleanRel := filepath.Clean(relPath)
	if filepath.IsAbs(cleanRel) {
		return "", fmt.Errorf("expected relative path, got absolute: %s", relPath)
	}

	joined := filepath.Join(absRoot, cleanRel)
	rel, err := filepath.Rel(absRoot, joined)
	if err != nil {
		return "", fmt.Errorf("failed to resolve path: %w", err)
	}

	if rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
		return "", fmt.Errorf("path escapes workspace: %s", relPath)
	}

	return joined, nil
}

// isWithinWorkspace reports whether candidate resides within the workspace root.
func isWithinWorkspace(root, candidate string) bool {
	cleanRoot, err := filepath.Abs(root)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Convert the path to workspace-relative before calling: filepath.Rel(root, absPath) and pass the result
  2. Use filepath.Rel inside a helper and fall back to this error when the path is outside the workspace
  3. Fix the upstream caller/config that supplies absolute paths to a function whose contract is relative paths
  4. If the file genuinely lives outside the workspace, copy or symlink it in rather than referencing it by absolute path

Example fix

// before
p, _ := safeWorkspacePath(root, "/home/user/project/.beads/config.json")
// after
rel, _ := filepath.Rel(root, "/home/user/project/.beads/config.json")
p, err := safeWorkspacePath(root, rel)
Defensive patterns

Strategy: validation

Validate before calling

if filepath.IsAbs(filepath.Clean(p)) {
    var err error
    p, err = filepath.Rel(root, p)
    if err != nil { return err }
}

Type guard

func isRelativePath(p string) bool {
    return !filepath.IsAbs(filepath.Clean(p))
}

Try / catch

p, err := safeWorkspacePath(root, rel)
if err != nil {
    if strings.HasPrefix(err.Error(), "expected relative path") {
        // normalize input to relative and retry once
    }
    return err
}

Prevention

When it happens

Trigger: Calling safeWorkspacePath with relPath such as "/etc/passwd" or "/home/user/.beads/db" — any value where filepath.Clean(relPath) begins with the OS path separator.

Common situations: Caller stored full paths in a config or database and passes them through unmodified; code built on Windows/macOS carries a leading "/" into a function expecting a repo-relative path; user-supplied input not validated before reaching fix helpers.

Related errors


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