{"record":{"id":"607b2d0d2781c6ec","repo":"JuliusBrussee/caveman","slug":"duplicate-provider-model-region-row-s-s-s","errorCode":null,"errorMessage":"duplicate provider/model/region row %s/%s@%s","messagePattern":"duplicate provider/model/region row (.+?)/(.+?)@(.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/catalog/catalog.go","lineNumber":299,"sourceCode":"\t\t\tif err != nil || capVerified.IsZero() {\n\t\t\t\treturn nil, fmt.Errorf(\"%s %s/%s: capabilities_verified_at must be RFC3339\", label, entry.Provider, entry.Model)\n\t\t\t}\n\t\t\tif capVerified.After(time.Now().UTC().Add(24 * time.Hour)) {\n\t\t\t\treturn nil, fmt.Errorf(\"%s %s/%s: capabilities_verified_at is in the future\", label, entry.Provider, entry.Model)\n\t\t\t}\n\t\t}\n\t\tif len(entry.Sources) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"%s %s/%s: at least one source is required\", label, entry.Provider, entry.Model)\n\t\t}\n\t\tfor _, rawURL := range entry.Sources {\n\t\t\tu, err := url.ParseRequestURI(rawURL)\n\t\t\tif err != nil || u.Scheme != \"https\" || u.Host == \"\" {\n\t\t\t\treturn nil, fmt.Errorf(\"%s %s/%s: invalid HTTPS source %q\", label, entry.Provider, entry.Model, rawURL)\n\t\t\t}\n\t\t}\n\t\tkey := entry.Provider + \"\\x00\" + entry.Model + \"\\x00\" + entry.Region\n\t\tif _, duplicate := seen[key]; duplicate {\n\t\t\treturn nil, fmt.Errorf(\"duplicate provider/model/region row %s/%s@%s\", entry.Provider, entry.Model, entry.Region)\n\t\t}\n\t\tseen[key] = struct{}{}\n\t}\n\treturn decoded, nil\n}\n\n// catalogCandidates lists the paths to try, in order: an explicit env override,\n// the deploy-image and CWD-relative locations, then a walk up from the working\n// directory so the catalog resolves when binaries or tests run from subdirs.\n// Both repo layouts are tried: the monorepo keeps the catalog under public/,\n// the published caveman repo has it at the top level.\nfunc catalogCandidates() []string {\n\trels := []string{\n\t\t\"public/shared/provider-catalog/catalog/current.yaml\",\n\t\t\"shared/provider-catalog/catalog/current.yaml\",\n\t}\n\tcandidates := []string{}\n\tif p := os.Getenv(\"CAVE_CATALOG_PATH\"); p != \"\" {","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/shared/platform/catalog/catalog.go#L281-L317","documentation":"Thrown by the catalog YAML loader when two entries share the same provider+model+region triple (checked via a seen-map keyed on the NUL-joined triple). The triple is the pricing lookup key, so duplicates would make lookups ambiguous; the load fails instead of letting one row silently shadow another.","triggerScenarios":"Two YAML list items with identical provider, model, and region (e.g. the same model pasted twice for a price update without deleting the old row, or the same provider+region added by two contributors).","commonSituations":"Appending an updated price row while leaving the stale row in place; merging catalog branches that both added the same model; a generator emitting one row per region and accidentally repeating a region.","solutions":["Search the file for the provider/model/region named in the error and delete or differentiate the duplicate rows.","When updating prices, edit the existing row in place (and refresh verified_at) instead of adding a new one.","If a provider genuinely has two price books for the same triple, that's unsupported — pick the correct row."],"exampleFix":"# before\n- provider: openai\n  model: gpt-5.6\n  region: us\n  pricing: {old}\n- provider: openai\n  model: gpt-5.6\n  region: us\n  pricing: {new}\n\n# after — single updated row\n- provider: openai\n  model: gpt-5.6\n  region: us\n  pricing: {new}\n  verified_at: \"2026-08-14T18:30:00Z\"","handlingStrategy":"validation","validationCode":"seen := map[string]bool{}\nfor _, e := range entries {\n    key := e.Provider + \"\\x00\" + e.Model + \"\\x00\" + e.Region\n    if seen[key] {\n        return fmt.Errorf(\"duplicate row %s/%s@%s\", e.Provider, e.Model, e.Region)\n    }\n    seen[key] = true\n}","typeGuard":null,"tryCatchPattern":"Fail catalog load at startup; duplicates are a data-authoring error, not something to dedupe silently (the two rows may disagree on price).","preventionTips":["Update existing rows in place instead of appending new ones for price changes.","After merges, run a quick duplicate scan keyed on provider/model/region.","Keep one canonical owner/generator for the catalog file to avoid parallel additions."],"tags":["catalog","yaml","duplicates","validation"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}