{"record":{"id":"41983a99cd90f46f","repo":"goharbor/harbor","slug":"missing-job-id-in-job-stats","errorCode":null,"errorMessage":"missing job ID in job stats","messagePattern":"missing job ID in job stats","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/jobservice/job/models.go","lineNumber":122,"sourceCode":"\tCheckIn  string     `json:\"check_in,omitempty\"`\n\tMetadata *StatsInfo `json:\"metadata,omitempty\"`\n}\n\n// 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 {","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/src/jobservice/job/models.go#L104-L140","documentation":"job.Stats.Validate (src/jobservice/job/models.go:122) requires Info.JobID to be non-empty. The ID is the primary key used later to retrieve, stop, and retry the job, so stats without it are unusable and rejected.","triggerScenarios":"Building job.Stats from a struct where the generated job UUID was never assigned; deserializing a stats payload whose info.job_id is missing or empty.","commonSituations":"Producers ignoring the ID returned at submission time; JSON key mismatches ('job_id' vs 'jobId'); test fixtures with placeholder empty IDs.","solutions":["Always set Info.JobID from the job UUID generated at submission before saving stats","Check the serialized JSON uses the exact 'job_id' key","Fix or discard the producer that wrote stats without an ID"],"exampleFix":"// before\nInfo: &job.StatsInfo{JobName: \"REPLICATION\"}\n// after\nInfo: &job.StatsInfo{\n    JobID:   \"ju-1234\",\n    JobName: \"REPLICATION\",\n}","handlingStrategy":"type-guard","validationCode":"if err := st.Validate(); err != nil {\n    return err // reject stats without a job ID before use\n}","typeGuard":"func hasStatsJobID(st *job.Stats) bool {\n    return st != nil && st.Info != nil && strings.TrimSpace(st.Info.JobID) != \"\"\n}","tryCatchPattern":null,"preventionTips":["Assign the submitted job's UUID to Info.JobID before saving stats","Verify serialized JSON uses the 'job_id' tag exactly","Validate stats at the boundary where they are read from the backend"],"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"}