{"record":{"id":"c9fe55bfe04c262a","repo":"goharbor/harbor","slug":"missing-job-name-in-job-stats","errorCode":null,"errorMessage":"missing job name in job stats","messagePattern":"missing job name in job stats","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/jobservice/job/models.go","lineNumber":126,"sourceCode":"// SimpleStatusChange only keeps job ID and the target status\ntype SimpleStatusChange struct {\n\tJobID        string `json:\"job_id\"`\n\tTargetStatus string `json:\"target_status\"`\n\tRevision     int64  `json:\"revision\"`\n}\n\n// Validate the job stats\nfunc (st *Stats) Validate() error {\n\tif st.Info == nil {\n\t\treturn errors.New(\"nil stats body\")\n\t}\n\n\tif utils.IsEmptyStr(st.Info.JobID) {\n\t\treturn errors.New(\"missing job ID in job stats\")\n\t}\n\n\tif utils.IsEmptyStr(st.Info.JobName) {\n\t\treturn errors.New(\"missing job name in job stats\")\n\t}\n\n\tif utils.IsEmptyStr(st.Info.JobKind) {\n\t\treturn errors.New(\"missing job name in job stats\")\n\t}\n\n\tif st.Info.JobKind != KindGeneric &&\n\t\tst.Info.JobKind != KindPeriodic &&\n\t\tst.Info.JobKind != KindScheduled {\n\t\treturn errors.Errorf(\"job kind is not supported: %s\", st.Info.JobKind)\n\t}\n\n\tstatus := Status(st.Info.Status)\n\tif err := status.Validate(); err != nil {\n\t\treturn err\n\t}\n\n\tif st.Info.JobKind == KindPeriodic {","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/src/jobservice/job/models.go#L108-L144","documentation":"job.Stats.Validate (src/jobservice/job/models.go:126) requires Info.JobName to be non-empty; the name maps a running execution back to its registered job implementation. Note: the identical message is reused a few lines later for an empty JobKind (a Harbor copy-paste), so the message alone does not prove which field is missing.","triggerScenarios":"Stats written with Info.JobName empty; deserializing a payload whose info.job_name is missing. If JobName is actually set, the same text fires for an empty info.job_kind instead.","commonSituations":"Producers copying stats structs without the name; refactored serializers renaming 'job_name'; fixtures trimmed for tests.","solutions":["Populate JobName with the registered job name when creating stats","If JobName is present, check Info.JobKind — the same error text covers it","Inspect the raw JSON payload to see which field is actually absent"],"exampleFix":"// before\nInfo: &job.StatsInfo{JobID: \"ju-1234\"}\n// after\nInfo: &job.StatsInfo{\n    JobID:   \"ju-1234\",\n    JobName: \"GARBAGE_COLLECTION\",\n    JobKind: job.KindGeneric,\n}","handlingStrategy":"type-guard","validationCode":"if err := st.Validate(); err != nil {\n    return err // rejects empty JobName AND empty JobKind with the same text\n}","typeGuard":"func hasStatsJobName(st *job.Stats) bool {\n    return st != nil && st.Info != nil && strings.TrimSpace(st.Info.JobName) != \"\" && strings.TrimSpace(st.Info.JobKind) != \"\"\n}","tryCatchPattern":null,"preventionTips":["Set JobName and JobKind together when building Stats — the error text covers both","On this message, dump the raw stats JSON to see which field is actually missing","Validate stats right after deserialization to catch producer schema drift early"],"tags":["jobservice","job-stats","validation","serialization","harbor"],"backgroundTag":null,"analyzedSha":"7b2fd08cc568955cca339afeefab27372840d936","analyzedAt":"2026-08-16T00:00:10.961Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}