JuliusBrussee/caveman · error
duplicate provider/model/region row %s/%s@%s
Error message
duplicate provider/model/region row %s/%s@%s
What it means
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.
Source
Thrown at shared/platform/catalog/catalog.go:299
if err != nil || capVerified.IsZero() {
return nil, fmt.Errorf("%s %s/%s: capabilities_verified_at must be RFC3339", label, entry.Provider, entry.Model)
}
if capVerified.After(time.Now().UTC().Add(24 * time.Hour)) {
return nil, fmt.Errorf("%s %s/%s: capabilities_verified_at is in the future", label, entry.Provider, entry.Model)
}
}
if len(entry.Sources) == 0 {
return nil, fmt.Errorf("%s %s/%s: at least one source is required", label, entry.Provider, entry.Model)
}
for _, rawURL := range entry.Sources {
u, err := url.ParseRequestURI(rawURL)
if err != nil || u.Scheme != "https" || u.Host == "" {
return nil, fmt.Errorf("%s %s/%s: invalid HTTPS source %q", label, entry.Provider, entry.Model, rawURL)
}
}
key := entry.Provider + "\x00" + entry.Model + "\x00" + entry.Region
if _, duplicate := seen[key]; duplicate {
return nil, fmt.Errorf("duplicate provider/model/region row %s/%s@%s", entry.Provider, entry.Model, entry.Region)
}
seen[key] = struct{}{}
}
return decoded, nil
}
// catalogCandidates lists the paths to try, in order: an explicit env override,
// the deploy-image and CWD-relative locations, then a walk up from the working
// directory so the catalog resolves when binaries or tests run from subdirs.
// Both repo layouts are tried: the monorepo keeps the catalog under public/,
// the published caveman repo has it at the top level.
func catalogCandidates() []string {
rels := []string{
"public/shared/provider-catalog/catalog/current.yaml",
"shared/provider-catalog/catalog/current.yaml",
}
candidates := []string{}
if p := os.Getenv("CAVE_CATALOG_PATH"); p != "" {View on GitHub (pinned to 27d5a3981a)
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.
Example fix
# before
- provider: openai
model: gpt-5.6
region: us
pricing: {old}
- provider: openai
model: gpt-5.6
region: us
pricing: {new}
# after — single updated row
- provider: openai
model: gpt-5.6
region: us
pricing: {new}
verified_at: "2026-08-14T18:30:00Z" Defensive patterns
Strategy: validation
Validate before calling
seen := map[string]bool{}
for _, e := range entries {
key := e.Provider + "\x00" + e.Model + "\x00" + e.Region
if seen[key] {
return fmt.Errorf("duplicate row %s/%s@%s", e.Provider, e.Model, e.Region)
}
seen[key] = true
} Try / catch
Fail catalog load at startup; duplicates are a data-authoring error, not something to dedupe silently (the two rows may disagree on price).
Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- ${label}:${lineNo}: "${text}" is not a finite number
- ${label}: row is missing a string "${field}"
- ${label}: ${key} has no pricing block
- {label}: duplicate identity {'/'.join(identity)}
- %s: provider, model, and region are required
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/607b2d0d2781c6ec.
Report an issue: GitHub.