{"record":{"id":"387997e5f61af3db","repo":"golang/go","slug":"duplicate-paths-s-and-s-in-overlay-map","errorCode":null,"errorMessage":"duplicate paths %s and %s in overlay map","messagePattern":"duplicate paths (.+?) and (.+?) in overlay map","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/cmd/go/internal/fsys/fsys.go","lineNumber":375,"sourceCode":"\t}\n\treturn initFromJSON(b)\n}\n\nfunc initFromJSON(js []byte) error {\n\tvar ojs overlayJSON\n\tif err := json.Unmarshal(js, &ojs); err != nil {\n\t\treturn fmt.Errorf(\"parsing overlay JSON: %v\", err)\n\t}\n\n\tseen := make(map[string]string)\n\tvar list []replace\n\tfor _, from := range slices.Sorted(maps.Keys(ojs.Replace)) {\n\t\tif from == \"\" {\n\t\t\treturn fmt.Errorf(\"empty string key in overlay map\")\n\t\t}\n\t\tafrom := abs(from)\n\t\tif old, ok := seen[afrom]; ok {\n\t\t\treturn fmt.Errorf(\"duplicate paths %s and %s in overlay map\", old, from)\n\t\t}\n\t\tseen[afrom] = from\n\t\tlist = append(list, replace{from: afrom, to: abs(ojs.Replace[from])})\n\t}\n\n\tslices.SortFunc(list, func(x, y replace) int { return cmp(x.from, y.from) })\n\n\tfor i, r := range list {\n\t\tif r.to == \"\" { // deleted\n\t\t\tcontinue\n\t\t}\n\t\t// have file for r.from; look for child file implying r.from is a directory\n\t\tprefix := r.from + string(filepath.Separator)\n\t\tfor _, next := range list[i+1:] {\n\t\t\tif !strings.HasPrefix(next.from, prefix) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif next.to != \"\" {","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/cmd/go/internal/fsys/fsys.go#L357-L393","documentation":"Returned by fsys.initFromJSON when two distinct source paths in the 'replace' map resolve (after abs()) to the same absolute path. The overlay system keys replacements by absolute path, so duplicates are ambiguous and rejected.","triggerScenarios":"An overlay with replace keys like \"foo.go\" and \"./foo.go\", or \"a/b.go\" alongside an absolute \"/cwd/a/b.go\", both producing the same abs path.","commonSituations":"Mixing relative and absolute spellings of the same path; symlinks where two different spellings collapse to one target; generators concatenating overlays without deduping.","solutions":["Open the overlay JSON and keep only one spelling of each source path under 'replace'.","Normalize every source path to absolute form (filepath.Abs) before emitting the overlay.","Deduplicate by absolute path with a seen-map when generating overlays.","Resolve symlinks (filepath.EvalSymlinks) before comparing if symlinks are involved."],"exampleFix":"// before — two spellings of the same file\n{ \"replace\": { \"a.go\": \"x.go\", \"./a.go\": \"y.go\" } }\n// after — single canonical spelling\n{ \"replace\": { \"a.go\": \"x.go\" } }","handlingStrategy":"validation","validationCode":"// Normalise and dedupe replace keys by absolute path before writing the overlay.\nseen := make(map[string]string)\nfor from, to := range ojs.Replace {\n    if from == \"\" { continue }\n    absFrom, _ := filepath.Abs(from)\n    if prev, ok := seen[absFrom]; ok {\n        return fmt.Errorf(\"overlay has duplicate source paths %q and %q -> %s\", prev, from, absFrom)\n    }\n    seen[absFrom] = from\n    _ = to\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Normalise every replace source to an absolute, symlink-evaluated path.","Dedupe with a seen-map when generating overlays programmatically.","Avoid mixing relative and absolute spellings of the same file."],"tags":["overlay","json","validation","filesystem","go-command"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}