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
- Convert the path to workspace-relative before calling: filepath.Rel(root, absPath) and pass the result
- Use filepath.Rel inside a helper and fall back to this error when the path is outside the workspace
- Fix the upstream caller/config that supplies absolute paths to a function whose contract is relative paths
- 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
- Normalize all paths to workspace-relative at the ingestion boundary
- Never pass user- or config-supplied absolute paths to relative-path APIs
- Document the relative-path contract in callers of safeWorkspacePath
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
- proxy.ForceStopUnverified: at most one options value is allo
- proxy.ForceStopUnverified: record has a verifiable v2 worksp
- newExternalProxiedServerUOWProvider: proxied server root (fr
- newProxiedServerUOWProvider: proxied server root (from env o
- BEADS_DIR points to unsafe location: %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/d5e88e363015fae8.
Report an issue: GitHub.