{"record":{"id":"624628249a0cc913","repo":"multica-ai/multica","slug":"failed-runtime-probes-must-not-include-counts","errorCode":null,"errorMessage":"failed runtime probes must not include counts","messagePattern":"failed runtime probes must not include counts","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"server/internal/handler/client_usage.go","lineNumber":194,"sourceCode":"func normalizeClientUsageOS(value string) string {\n\tvalue = strings.ToLower(strings.TrimSpace(value))\n\tswitch value {\n\tcase \"macos\", \"windows\", \"linux\", \"ios\", \"android\", \"chromeos\":\n\t\treturn value\n\tdefault:\n\t\treturn \"unknown\"\n\t}\n}\n\nfunc validateClientUsageRuntime(probe clientUsageRuntimeProbe) (validatedRuntimeProbe, error) {\n\tresult := strings.ToLower(strings.TrimSpace(probe.ProbeResult))\n\tif result != \"success\" && result != \"error\" {\n\t\treturn validatedRuntimeProbe{}, errors.New(\"runtime probe_result must be success or error\")\n\t}\n\tvalidated := validatedRuntimeProbe{Result: pgtype.Text{String: result, Valid: true}}\n\tif result == \"error\" {\n\t\tif probe.RuntimeCount != nil || probe.ProviderSummary != nil || probe.OnlineCount != nil || probe.OfflineCount != nil {\n\t\t\treturn validatedRuntimeProbe{}, errors.New(\"failed runtime probes must not include counts\")\n\t\t}\n\t\treturn validated, nil\n\t}\n\n\tif probe.RuntimeCount == nil || probe.ProviderSummary == nil || probe.OnlineCount == nil || probe.OfflineCount == nil {\n\t\treturn validatedRuntimeProbe{}, errors.New(\"successful runtime probes require all counts\")\n\t}\n\tif *probe.RuntimeCount < 0 || *probe.RuntimeCount > 1000 || *probe.OnlineCount < 0 || *probe.OfflineCount < 0 || *probe.OnlineCount+*probe.OfflineCount != *probe.RuntimeCount {\n\t\treturn validatedRuntimeProbe{}, errors.New(\"invalid runtime counts\")\n\t}\n\tif len(probe.ProviderSummary) > 32 {\n\t\treturn validatedRuntimeProbe{}, errors.New(\"too many runtime providers\")\n\t}\n\tvar providerTotal int64\n\tfor provider, count := range probe.ProviderSummary {\n\t\tif !providerNamePattern.MatchString(provider) || count < 0 || count > 1000 {\n\t\t\treturn validatedRuntimeProbe{}, errors.New(\"invalid runtime provider summary\")\n\t\t}","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/handler/client_usage.go#L176-L212","documentation":"validateClientUsageRuntime enforces mutual exclusion: when probe_result is 'error', none of runtime_count, provider_summary, online_count, offline_count may be present (non-nil). A failed probe has nothing meaningful to count, so attaching counts is treated as a contract violation rather than ignored — this keeps the stored telemetry unambiguous. The check uses pointer nil-ness, so an explicit JSON null is acceptable; only present-with-value fields fail.","triggerScenarios":"POST a probe with probe_result=\"error\" plus any of {\"runtime_count\":0}, {\"provider_summary\":{}}, {\"online_count\":0}, {\"offline_count\":0}. Note {} for provider_summary counts as present and fails; explicit \"provider_summary\":null does not.","commonSituations":"Client reusing one struct for both outcomes and zero-filling fields instead of omitting them; JSON serializer configured to emit zeros rather than omitempty; a 'best effort' client that includes whatever counts it happened to collect before the failure.","solutions":["On the error path, omit all four count/summary fields entirely (use omitempty / conditional serialization).","Use explicit JSON nulls rather than {} or 0 if your serializer cannot skip fields.","Build the request payload with a discriminated builder that branches on probe outcome.","Add a contract test: error probes must serialize without any count keys."],"exampleFix":"// before\ntype probePayload struct {\n    Result          string           `json:\"probe_result\"`\n    RuntimeCount    *int32           `json:\"runtime_count\"`\n}\np := probePayload{Result: \"error\", RuntimeCount: &zero}\n\n// after\ntype probePayload struct {\n    Result          string           `json:\"probe_result\"`\n    RuntimeCount    *int32           `json:\"runtime_count,omitempty\"`\n}\np := probePayload{Result: \"error\"} // leave counts nil","handlingStrategy":"type-guard","validationCode":"function buildProbePayload(probe) {\n  const result = normalizeProbeResult(probe.result);\n  if (result === 'error') {\n    // must include NO count fields at all\n    return { probe_result: result };\n  }\n  return {\n    probe_result: result,\n    runtime_count: probe.runtimeCount,\n    online_count: probe.onlineCount,\n    offline_count: probe.offlineCount,\n    provider_summary: probe.providerSummary,\n  };\n}","typeGuard":"const isErrorProbe = (p) => normalizeProbeResult(p.result) === 'error';\n\nconst errorProbeIsClean = (p) => isErrorProbe(p) && p.runtime_count === undefined && p.provider_summary === undefined && p.online_count === undefined && p.offline_count === undefined;","tryCatchPattern":null,"preventionTips":["Build the payload with a discriminated function per outcome instead of one do-everything struct.","Audit JSON tags: the four count/summary fields must be omitempty on the error path or absent from the error payload type.","Prefer explicit JSON null over {} only if your serializer cannot omit — but omission is cleaner."],"tags":["validation","telemetry","contract","http-400"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}