{"record":{"id":"8d20988d7b381213","repo":"gatsbyjs/gatsby","slug":"an-id-must-be-provided-when-ending-a-job","errorCode":null,"errorMessage":"An ID must be provided when ending a job","messagePattern":"An ID must be provided when ending a job","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/gatsby/src/redux/reducers/jobs.ts","lineNumber":38,"sourceCode":"          ...action.payload,\n          createdAt: Date.now(),\n          plugin: action.plugin,\n        })\n\n        state.active[index] = mergedJob\n        return state\n      } else {\n        state.active.push({\n          ...action.payload,\n          createdAt: Date.now(),\n          plugin: action.plugin,\n        })\n        return state\n      }\n    }\n    case `END_JOB`: {\n      if (!action.payload.id) {\n        throw new Error(`An ID must be provided when ending a job`)\n      }\n      const completedAt = Date.now()\n      const index = _.findIndex(state.active, j => j.id === action.payload.id)\n      if (index === -1) {\n        throw new Error(oneLine`\n          The plugin \"${_.get(action, `plugin.name`, `anonymous`)}\"\n          tried to end a job with the id \"${action.payload.id}\"\n          that either hasn't yet been created or has already been ended`)\n      }\n      const job = state.active.splice(index, 1)[0]\n      state.done.push({\n        ...job,\n        completedAt,\n        runTime: moment(completedAt).diff(moment(job.createdAt)),\n      })\n\n      return state\n    }","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/gatsbyjs/gatsby/blob/8b06340921ffdf23125a365b9c9923690cb62ce6/packages/gatsby/src/redux/reducers/jobs.ts#L20-L56","documentation":"The v1 jobs reducer's END_JOB case requires action.payload.id to identify which active job to complete. It throws immediately if id is falsy, before looking up the job, because there is no way to splice the right entry out of state.jobs.active or compute its runTime without it.","triggerScenarios":"Dispatching an END_JOB action (typically via the internal actions.endJob helper) with a missing or empty payload.id. Reached when a plugin tries to mark a job complete without specifying which job.","commonSituations":"Custom/third-party plugin using the legacy endJob API without passing the original job id; refactoring that drops the id variable; race where the id variable is undefined at the call site.","solutions":["Pass the same `id` used at creation time to endJob.","Track job ids in a map when you create them so they are available at completion.","Migrate to createJobV2 (v2 API) which manages lifecycle via content digest."],"exampleFix":"// before\nactions.endJob({}) // forgot id\n// after\nactions.endJob({ id: jobId })","handlingStrategy":"validation","validationCode":"function endJobSafe(actions, jobOrId) {\n  const id = typeof jobOrId === 'string' ? jobOrId : jobOrId?.id\n  if (!id) throw new Error('endJob: id required')\n  return actions.endJob({ id })\n}","typeGuard":"function hasJobId(x) {\n  return typeof x === 'string' ? x.length > 0 : !!x?.id\n}","tryCatchPattern":null,"preventionTips":["Keep a reference to the job id returned/used at creation.","Migrate to the v2 job API which keys by content digest.","Add boundary validation for any internal redux actions you wrap."],"tags":["jobs","reducer","validation","plugin-api"],"backgroundTag":null,"analyzedSha":"8b06340921ffdf23125a365b9c9923690cb62ce6","analyzedAt":"2026-08-13T02:36:21.405Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}