{"record":{"id":"ad690f505f3aaa6c","repo":"sipeed/picoclaw","slug":"source-and-target-must-be-absolute-paths","errorCode":null,"errorMessage":"source and target must be absolute paths","messagePattern":"source and target must be absolute paths","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/isolation/runtime.go","lineNumber":181,"sourceCode":"func ValidateExposePaths(items []config.ExposePath) error {\n\tseen := map[string]struct{}{}\n\tfor _, item := range items {\n\t\tif item.Source == \"\" {\n\t\t\treturn fmt.Errorf(\"source is required\")\n\t\t}\n\t\tif item.Mode != \"ro\" && item.Mode != \"rw\" {\n\t\t\treturn fmt.Errorf(\"invalid expose_paths mode: %s\", item.Mode)\n\t\t}\n\n\t\tsource := filepath.Clean(item.Source)\n\t\ttarget := item.Target\n\t\tif target == \"\" {\n\t\t\ttarget = source\n\t\t}\n\t\ttarget = filepath.Clean(target)\n\n\t\tif !filepath.IsAbs(source) || !filepath.IsAbs(target) {\n\t\t\treturn fmt.Errorf(\"source and target must be absolute paths\")\n\t\t}\n\t\tif _, ok := seen[target]; ok {\n\t\t\treturn fmt.Errorf(\"duplicate expose_path target: %s\", target)\n\t\t}\n\t\tseen[target] = struct{}{}\n\t}\n\treturn nil\n}\n\n// NormalizeExposePath fills implicit defaults and cleans path values so merge\n// and validation logic can work with canonical paths.\nfunc NormalizeExposePath(item config.ExposePath) config.ExposePath {\n\tsource := filepath.Clean(item.Source)\n\ttarget := item.Target\n\tif target == \"\" {\n\t\ttarget = source\n\t}\n\treturn config.ExposePath{","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/isolation/runtime.go#L163-L199","documentation":"After filepath.Clean (and defaulting target to source when target is empty), ValidateExposePaths requires both source and target to be absolute paths. Relative paths — including \"~/...\" which Clean does not expand — make the exposure rule ambiguous against the child's redirected filesystem, so preflight rejects them.","triggerScenarios":"source: ~/project (tilde is a relative path to filepath.IsAbs); source: ./data or data; target: workspace (relative); a Windows-style path like C:\\data evaluated on Linux where filepath.IsAbs expects a leading /.","commonSituations":"Shell-style habits (~) carried into config; configs moved between Windows and Linux hosts where the absoluteness rules differ; relative paths that worked in a non-isolated mode and were never made absolute.","solutions":["Expand ~ yourself and write the full absolute path (e.g. /home/me/project)","Give every entry an explicit absolute target when it differs from the source","On Linux, ensure paths start with /; on Windows, use drive-letter or UNC absolute paths","If generating config programmatically, run filepath.Abs on values before serializing"],"exampleFix":"# before\nisolation:\n  expose_paths:\n    - source: ~/project\n      mode: ro\n\n# after\nisolation:\n  expose_paths:\n    - source: /home/me/project\n      mode: ro","handlingStrategy":"validation","validationCode":"for _, p := range cfg.Isolation.ExposePaths {\n    src := p.Source\n    if strings.HasPrefix(src, \"~\" + string(os.PathSeparator)) || src == \"~\" {\n        if home, err := os.UserHomeDir(); err == nil {\n            src = filepath.Join(home, strings.TrimPrefix(src, \"~\"))\n        }\n    }\n    if !filepath.IsAbs(src) {\n        abs, err := filepath.Abs(src)\n        if err != nil {\n            return fmt.Errorf(\"cannot make expose source absolute: %s\", p.Source)\n        }\n        src = abs\n    }\n    p.Source = src\n    // repeat for target, then validate\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Expand ~ and make paths absolute in your config loader before they reach isolation","Remember absolute means leading / on unix and drive/UNC paths on windows — write per-OS examples in docs"],"tags":["config","validation","paths","expose-paths"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}