{"record":{"id":"d56efa5a990dacf3","repo":"sipeed/picoclaw","slug":"duplicate-expose-path-target-s","errorCode":null,"errorMessage":"duplicate expose_path target: %s","messagePattern":"duplicate expose_path target: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/isolation/runtime.go","lineNumber":184,"sourceCode":"\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{\n\t\tSource: source,\n\t\tTarget: filepath.Clean(target),\n\t\tMode:   item.Mode,","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/isolation/runtime.go#L166-L202","documentation":"ValidateExposePaths tracks cleaned target paths in a set; a second expose_paths entry resolving to the same target (explicitly or by defaulting to its source) is rejected. Duplicate mount targets would make the mapping order-dependent, so preflight fails with the colliding path.","triggerScenarios":"Two entries with the same target; or two entries whose target keys are empty so both default to their (identical) source paths; entries that differ in textual form but Clean to the same path (e.g. /a/b and /a//b/).","commonSituations":"Appending a new rule without noticing an existing one mounts the same location; YAML anchors or copy-paste duplication; merging config layers that each add a rule for the same directory.","solutions":["Remove or merge the duplicate rules so each target appears once","If two sources must both be visible, give them distinct targets (e.g. /workspace/a and /workspace/b)","Normalize paths first: remember comparison happens on filepath.Clean values, so cosmetic differences do not make entries distinct"],"exampleFix":"# before\nisolation:\n  expose_paths:\n    - {source: /home/me/project, mode: rw}\n    - {source: /home/me/project, target: /project, mode: ro}\n\n# after\nisolation:\n  expose_paths:\n    - {source: /home/me/project, mode: rw}","handlingStrategy":"validation","validationCode":"seen := map[string]struct{}{}\nfor _, p := range cfg.Isolation.ExposePaths {\n    target := p.Target\n    if target == \"\" {\n        target = p.Source\n    }\n    target = filepath.Clean(target)\n    if _, dup := seen[target]; dup {\n        return fmt.Errorf(\"duplicate expose target %s — merge or split targets\", target)\n    }\n    seen[target] = struct{}{}\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Dedupe on cleaned target paths whenever merging config layers that add expose rules","Prefer mapping several sources to distinct subdirectory targets instead of one shared target"],"tags":["config","validation","expose-paths"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}