{"record":{"id":"d5e88e363015fae8","repo":"gastownhall/beads","slug":"expected-relative-path-got-absolute-s","errorCode":null,"errorMessage":"expected relative path, got absolute: %s","messagePattern":"expected relative path, got absolute: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/doctor/fix/common.go","lineNumber":111,"sourceCode":"func localWorkspaceBeadsDir(path string) (string, error) {\n\tabsPath, err := filepath.Abs(path)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"invalid path: %w\", err)\n\t}\n\treturn filepath.Join(absPath, \".beads\"), nil\n}\n\n// safeWorkspacePath resolves relPath within the workspace root and ensures it\n// cannot escape the workspace via path traversal.\nfunc safeWorkspacePath(root, relPath string) (string, error) {\n\tabsRoot, err := filepath.Abs(root)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"invalid workspace path: %w\", err)\n\t}\n\n\tcleanRel := filepath.Clean(relPath)\n\tif filepath.IsAbs(cleanRel) {\n\t\treturn \"\", fmt.Errorf(\"expected relative path, got absolute: %s\", relPath)\n\t}\n\n\tjoined := filepath.Join(absRoot, cleanRel)\n\trel, err := filepath.Rel(absRoot, joined)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to resolve path: %w\", err)\n\t}\n\n\tif rel == \"..\" || strings.HasPrefix(rel, \"..\"+string(os.PathSeparator)) {\n\t\treturn \"\", fmt.Errorf(\"path escapes workspace: %s\", relPath)\n\t}\n\n\treturn joined, nil\n}\n\n// isWithinWorkspace reports whether candidate resides within the workspace root.\nfunc isWithinWorkspace(root, candidate string) bool {\n\tcleanRoot, err := filepath.Abs(root)","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/doctor/fix/common.go#L93-L129","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\np, _ := safeWorkspacePath(root, \"/home/user/project/.beads/config.json\")\n// after\nrel, _ := filepath.Rel(root, \"/home/user/project/.beads/config.json\")\np, err := safeWorkspacePath(root, rel)","handlingStrategy":"validation","validationCode":"if filepath.IsAbs(filepath.Clean(p)) {\n    var err error\n    p, err = filepath.Rel(root, p)\n    if err != nil { return err }\n}","typeGuard":"func isRelativePath(p string) bool {\n    return !filepath.IsAbs(filepath.Clean(p))\n}","tryCatchPattern":"p, err := safeWorkspacePath(root, rel)\nif err != nil {\n    if strings.HasPrefix(err.Error(), \"expected relative path\") {\n        // normalize input to relative and retry once\n    }\n    return err\n}","preventionTips":["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"],"tags":["go","path-validation","api-misuse"],"backgroundTag":"absolute-path-not-allowed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}