{"record":{"id":"b70ef8ed6f536299","repo":"goharbor/harbor","slug":"10013","errorCode":"10013","errorMessage":"empty job ID","messagePattern":"empty job ID","errorType":"validation","errorClass":"badRequestError","httpStatus":400,"severity":"error","filePath":"src/jobservice/core/controller.go","lineNumber":105,"sourceCode":"\t\t\treq.Job.Metadata.IsUnique,\n\t\t\treq.Job.StatusHook,\n\t\t)\n\t}\n\n\t// Save job stats\n\tif err == nil {\n\t\tif err := bc.manager.SaveJob(res); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn\n}\n\n// GetJob is implementation of same method in core interface.\nfunc (bc *basicController) GetJob(jobID string) (*job.Stats, error) {\n\tif utils.IsEmptyStr(jobID) {\n\t\treturn nil, errs.BadRequestError(errors.New(\"empty job ID\"))\n\t}\n\n\treturn bc.manager.GetJob(jobID)\n}\n\n// StopJob is implementation of same method in core interface.\nfunc (bc *basicController) StopJob(jobID string) error {\n\tif utils.IsEmptyStr(jobID) {\n\t\treturn errs.BadRequestError(errors.New(\"empty job ID\"))\n\t}\n\n\treturn bc.backendWorker.StopJob(jobID)\n}\n\n// RetryJob is implementation of same method in core interface.\nfunc (bc *basicController) RetryJob(jobID string) error {\n\tif utils.IsEmptyStr(jobID) {\n\t\treturn errs.BadRequestError(errors.New(\"empty job ID\"))","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/src/jobservice/core/controller.go#L87-L123","documentation":"basicController.GetJob (src/jobservice/core/controller.go:105) rejects an empty job ID before querying the manager, wrapping it with errs.BadRequestError — code 10013, the job service's equivalent of HTTP 400. It signals a client bug: the identifier never made it into the call.","triggerScenarios":"Calling core.Controller.GetJob with '' or a whitespace-only string; via REST, GET /api/v2.0/jobservice/jobs/{job_id} where the path parameter resolves to empty (collapsed double slash, malformed client URL).","commonSituations":"Client submits a job, ignores the error in the submit response, then queries with a never-assigned variable; JSON field mismatch (job_id vs jobId) leaves the value empty; URL assembled by concatenation with a missing segment.","solutions":["Guard the call site: fail fast when jobID is empty","Trace the empty value upstream — usually a failed submission or a deserialization field-name mismatch","Log the raw request when it happens to catch routing/client bugs"],"exampleFix":"// before\nstats, err := ctrl.GetJob(jobID)\n// after\nif utils.IsEmptyStr(jobID) {\n    return nil, fmt.Errorf(\"job ID is required to fetch job stats\")\n}\nstats, err := ctrl.GetJob(jobID)","handlingStrategy":"validation","validationCode":"func requireJobID(jobID string) error {\n    if strings.TrimSpace(jobID) == \"\" {\n        return fmt.Errorf(\"job ID must not be empty\")\n    }\n    return nil\n}","typeGuard":"func isNonEmptyJobID(id string) bool {\n    return strings.TrimSpace(id) != \"\"\n}","tryCatchPattern":"stats, err := ctrl.GetJob(jobID)\nif err != nil {\n    if errs.IsBadRequestError(err) { // code 10013\n        // client error: surface 400, do not retry\n    }\n    // otherwise treat as server-side and handle normally\n}","preventionTips":["Persist the job ID from every successful submission before making follow-up calls","Validate identifiers at the API boundary, not deep in handlers","Treat code 10013 as non-retryable — retrying an empty ID never succeeds"],"tags":["jobservice","api","validation","bad-request","go","harbor"],"backgroundTag":null,"analyzedSha":"7b2fd08cc568955cca339afeefab27372840d936","analyzedAt":"2026-08-16T00:00:10.961Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}