{"record":{"id":"0289bca6c58531cf","repo":"JuliusBrussee/caveman","slug":"usage-unlink-needs-claude-codex","errorCode":null,"errorMessage":"usage unlink needs claude|codex","messagePattern":"usage unlink needs claude\\|codex","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/cmd/caveman-proxy/main.go","lineNumber":530,"sourceCode":"\t\t\t}\n\t\t\tprintJSON(out)\n\t\tdefault:\n\t\t\tfatalJSON(logger, fmt.Errorf(\"unknown usage link source: %s\", provider))\n\t\t}\n\tcase \"unlink\":\n\t\tswitch provider {\n\t\tcase \"claude\", \"anthropic\":\n\t\t\tif err := spend.DeleteQuotaProvider(\"anthropic\"); err != nil {\n\t\t\t\tfatalJSON(logger, err)\n\t\t\t}\n\t\t\tprintJSON(map[string]any{\"unlinked\": \"claude\"})\n\t\tcase \"codex\", \"openai\":\n\t\t\tif err := spend.DeleteQuotaProvider(\"openai\"); err != nil {\n\t\t\t\tfatalJSON(logger, err)\n\t\t\t}\n\t\t\tprintJSON(map[string]any{\"unlinked\": \"codex\"})\n\t\tdefault:\n\t\t\tfatalJSON(logger, fmt.Errorf(\"usage unlink needs claude|codex\"))\n\t\t}\n\tdefault:\n\t\tfatalJSON(logger, fmt.Errorf(\"unknown usage subcommand: %s\", sub))\n\t}\n}\n\nfunc runLearn(logger *slog.Logger, args []string) {\n\tsub := \"scan\"\n\tif len(args) > 0 {\n\t\tsub = args[0]\n\t\targs = args[1:]\n\t}\n\thome := mustHome(logger)\n\tspend := mustStore(logger, home)\n\tdefer spend.Close()\n\tcwd, _ := os.Getwd()\n\tsources := strings.Split(argFlag(args, \"--sources\", \"codex,claude,caveman\"), \",\")\n\tsince := argFlag(args, \"--since\", \"30d\")","sourceCodeStart":512,"sourceCodeEnd":548,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/cmd/caveman-proxy/main.go#L512-L548","documentation":"The `usage unlink` subcommand got a provider that is not claude/anthropic or codex/openai. Unlink deletes a quota provider row (DeleteQuotaProvider('anthropic'|'openai')), and only those two backends exist, so anything else exits 1 with this message.","triggerScenarios":"`caveman-proxy usage unlink gemini` or any other provider name. Note unlink is the one place aliases are accepted (anthropic→claude, openai→codex), which makes typo'd third names the common residue.","commonSituations":"Copy-pasting a provider list configured for the gateway (gemini/vertex/bedrock) into unlink; cleanup scripts iterating all known providers instead of the two linkable ones.","solutions":["Unlink only claude (or anthropic) or codex (or openai)","If nothing is linked for that provider there is nothing to unlink — verify with your stats/usage read path first","Restrict cleanup scripts to the two supported keys"],"exampleFix":"# before\ncaveman-proxy usage unlink gemini\n\n# after\ncaveman-proxy usage unlink claude","handlingStrategy":"validation","validationCode":"case \"$PROVIDER\" in claude|anthropic|codex|openai) caveman-proxy usage unlink \"$PROVIDER\";; *) echo \"unlink supports claude|codex\" >&2;; esac","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only two quota providers exist to unlink: anthropic (claude) and openai (codex)","Filter generic provider lists down to the linkable pair before cleanup scripts","Check current links via your stats read path before unlinking"],"tags":["cli","usage-error","provider-validation","exit-1"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}