{"record":{"id":"d853386c9ea6f13c","repo":"JuliusBrussee/caveman","slug":"sink-q-has-no-measurable-fix-kind-pass-fix-kind-explicitly","errorCode":null,"errorMessage":"sink %q has no measurable fix kind; pass --fix-kind explicitly","messagePattern":"sink %q has no measurable fix kind; pass --fix-kind explicitly","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/store/learn_outcomes.go","lineNumber":77,"sourceCode":"\t\tif plan.Sinks[i].SinkID == sinkID {\n\t\t\tliveSink = &plan.Sinks[i]\n\t\t\tbreak\n\t\t}\n\t}\n\tappliedAtTime := learnOutcomeClock().UTC()\n\t// A scan run after the user's edit is post-fix evidence. Prefer newest report\n\t// snapshot strictly older than this plan's scan; use live plan only when no\n\t// older snapshot contains the sink. Missing live sinks may use snapshot only.\n\tsink, beforeSource := beforeSinkForAppliedFix(plan, sinkID, liveSink, appliedAtTime)\n\tif sink == nil {\n\t\treturn AppliedFixRecord{}, fmt.Errorf(\"sink %q not found in current learn plan\", sinkID)\n\t}\n\tfixKind = strings.TrimSpace(fixKind)\n\tif fixKind == \"\" {\n\t\tfixKind = defaultFixKind(*sink)\n\t}\n\tif fixKind == \"\" {\n\t\treturn AppliedFixRecord{}, fmt.Errorf(\"sink %q has no measurable fix kind; pass --fix-kind explicitly\", sinkID)\n\t}\n\tif len(fixKind) > 128 {\n\t\treturn AppliedFixRecord{}, fmt.Errorf(\"fix kind exceeds 128 bytes\")\n\t}\n\tif len(note) > 4096 {\n\t\treturn AppliedFixRecord{}, fmt.Errorf(\"note exceeds 4096 bytes\")\n\t}\n\tappliedAt := appliedAtTime.Format(time.RFC3339)\n\tbeforeEvidence := make(map[string]any, len(sink.Evidence)+1)\n\tfor key, value := range sink.Evidence {\n\t\tbeforeEvidence[key] = value\n\t}\n\tbeforeEvidence[\"before_source\"] = beforeSource\n\tevidence, err := json.Marshal(beforeEvidence)\n\tif err != nil {\n\t\treturn AppliedFixRecord{}, fmt.Errorf(\"encode before evidence: %w\", err)\n\t}\n\t// Fingerprint the edited artifact as it stands NOW — which at","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/internal/store/learn_outcomes.go#L59-L95","documentation":"RecordAppliedFix derives a default fix kind from the sink via defaultFixKind; if the caller passed none and the sink has no measurable default, recording is rejected because the outcome analysis needs a fix kind to bucket results.","triggerScenarios":"Calling Store.RecordAppliedFix with an empty fixKind for a sink whose defaultFixKind returns \"\" — the sink lacks the properties (e.g. timeout or similar measurable knob) that imply a fix kind.","commonSituations":"User runs 'proxy learn record-fix <sink>' without --fix-kind on a sink with no inherent measurable fix; a new sink type added to the plan that defaultFixKind does not recognize yet.","solutions":["Pass an explicit fix kind via the --fix-kind flag (e.g. the CLI's supported values).","Check len/trim: the value must be non-empty after trimming and at most 128 bytes.","Add support for the new sink type in defaultFixKind so a measurable default is derived.","Record the fix against a sink that does have a measurable fix kind if the current one is irrelevant."],"exampleFix":"// before\nproxy learn record-fix --sink sql-slow --note \"added index\"\n// after\nproxy learn record-fix --sink sql-slow --fix-kind timeout --note \"added index\"","handlingStrategy":"validation","validationCode":"fixKind = strings.TrimSpace(fixKind)\nif fixKind == \"\" { fixKind = \"timeout\" } // or require --fix-kind\nif fixKind == \"\" || len(fixKind) > 128 { return errors.New(\"invalid fix kind\") }","typeGuard":null,"tryCatchPattern":"_, err := store.RecordAppliedFix(plan, liveSink, sinkID, fixKind, note)\nif err != nil && strings.Contains(err.Error(), \"no measurable fix kind\") {\n    return fmt.Errorf(\"pass --fix-kind explicitly for sink %q\", sinkID)\n}","preventionTips":["Always pass --fix-kind in scripted/CI usage","Extend defaultFixKind for new sink types","Keep fixKind <= 128 bytes and trimmed"],"tags":["go","validation","argument","learn"],"backgroundTag":"missing-required-argument","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}