{"record":{"id":"33ef50d5a5ebaa7f","repo":"gatsbyjs/gatsby","slug":"an-id-must-be-provided-when-creating-or-setting-jo","errorCode":null,"errorMessage":"An ID must be provided when creating or setting job","messagePattern":"An ID must be provided when creating or setting job","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/gatsby/src/redux/reducers/jobs.ts","lineNumber":15,"sourceCode":"import _ from \"lodash\"\nimport { oneLine } from \"common-tags\"\nimport moment from \"moment\"\n\nimport { IGatsbyState, ActionsUnion } from \"../types\"\n\nexport const jobsReducer = (\n  state: IGatsbyState[\"jobs\"] = { active: [], done: [] },\n  action: ActionsUnion\n): IGatsbyState[\"jobs\"] => {\n  switch (action.type) {\n    case `CREATE_JOB`:\n    case `SET_JOB`: {\n      if (!action.payload.id) {\n        throw new Error(`An ID must be provided when creating or setting job`)\n      }\n      const index = _.findIndex(state.active, j => j.id === action.payload.id)\n      if (index !== -1) {\n        const mergedJob = _.merge(state.active[index], {\n          ...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","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/gatsbyjs/gatsby/blob/8b06340921ffdf23125a365b9c9923690cb62ce6/packages/gatsby/src/redux/reducers/jobs.ts#L1-L33","documentation":"The jobs reducer (v1 job API) handles CREATE_JOB and SET_JOB by merging or pushing into state.jobs.active, keyed by payload.id. It throws at the top of that case if action.payload.id is falsy, because without an ID the job cannot be found, merged, deduplicated, or later ended. The ID is the correlation key for the entire job lifecycle.","triggerScenarios":"Dispatching (or having a plugin dispatch) a CREATE_JOB / SET_JOB action whose payload lacks `id`. Normally reached only via internal helper APIs like actions.createJob / actions.setJob that wrap the dispatch; passing no/empty id to those wrappers reproduces it.","commonSituations":"A custom plugin calling the deprecated internal job API (jobs.createJob/setJob) without an id; a third-party plugin written against an older internal API surface; programmatic misuse of the redux store directly.","solutions":["Always pass a stable, unique `id` (e.g. derived from a content digest) when creating/setting a job.","Prefer the v2 job API (createJobV2) over the legacy jobs actions; it validates inputs differently.","If you do not need background job tracking, remove the call entirely."],"exampleFix":"// before\nactions.createJob({ name: `resize`, outputDir }) // no id\n// after\nactions.createJob({ id: createContentDigest({ outputDir, input }), name: `resize`, outputDir })","handlingStrategy":"validation","validationCode":"// Always derive a stable id before createJob/setJob.\nconst crypto = require('crypto')\nfunction jobId(input) {\n  if (!input || (typeof input !== 'object')) throw new Error('job input required')\n  return input.id || crypto.createHash('sha1').update(JSON.stringify(input)).digest('hex')\n}\n// actions.createJob({ id: jobId(payload), ...payload })","typeGuard":"function isValidJobPayload(p) {\n  return !!p && typeof p === 'object' && typeof p.id === 'string' && p.id.length > 0\n}","tryCatchPattern":null,"preventionTips":["Generate ids from a content digest so they are stable and unique.","Prefer the v2 createJobV2 API over the legacy jobs helpers.","Validate required action fields at the boundary of your own plugin code."],"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"}