payloadcms/payload · error · TaskError

Task handler threw an error

Error message

Task handler threw an error

What it means

Thrown as a wrapped `TaskError` when the task handler function throws during execution. The runner catches any non-cancellation error (`JobCancelledError`/`JobRunAbortedError` are rethrown) and re-emits it as a TaskError carrying the original `err.message`, plus task/job context, input, and the resolved retries config. This is the user-facing surface of a handler bug.

Source

Thrown at packages/payload/src/queues/operations/runJobs/runJob/getRunTaskFunction.ts:154

            inlineTask: getRunTaskFunction(job, workflowConfig, req, true, updateJob, {
              taskID,
              taskSlug,
            }),
            input,
            job: job as unknown as Job<WorkflowSlug>,
            req,
            tasks: getRunTaskFunction(job, workflowConfig, req, false, updateJob, {
              taskID,
              taskSlug,
            }),
          })
        )?.output
      } catch (err: any) {
        if (err instanceof JobCancelledError || err instanceof JobRunAbortedError) {
          // Job run aborts are handled by the top-level runner.
          throw err
        }
        throw new TaskError({
          executedAt,
          input: input!,
          job,
          message: err.message || 'Task handler threw an error',
          output,
          parent,
          retriesConfig: finalRetriesConfig,
          taskConfig,
          taskID,
          taskSlug,
          taskStatus,
          workflowConfig,
        })
      }

      if (taskConfig?.onSuccess) {
        await taskConfig.onSuccess({
          input,

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Inspect the TaskError's `message` and `output` (often the original error) to find the root cause.
  2. Fix the handler's failure mode; add input validation and defensive checks.
  3. Configure task `retries` so transient handler failures are retried instead of failing the job.

Example fix

// before
handler: async ({ input }) => {
  return JSON.parse(input.body) // throws on bad input
}

// after
handler: async ({ input }) => {
  try {
    return JSON.parse(input.body)
  } catch {
    throw new Error(`Invalid JSON in task input: ${input.body}`)
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// make handlers defensive and validate inputs
handler: async ({ input }) => {
  if (!input || typeof input.body !== 'string') {
    throw new Error('Task input.body is required and must be a string')
  }
  return JSON.parse(input.body)
}

Type guard

function isTaskError(err: unknown): err is { message: string; taskSlug?: string } {
  return err instanceof Error && 'taskConfig' in (err as object)
}

Try / catch

try {
  await runJobs({ req })
} catch (err) {
  if (isTaskError(err)) {
    // inspect err.message + err.output; fix handler or bump task.retries
    logger.error({ task: err.taskSlug, msg: err.message })
  }
  throw err
}

Prevention

When it happens

Trigger: The task handler threw — null deref, failed DB write, rejected fetch, business-logic exception, etc. The error propagates with the handler's message (or 'Task handler threw an error' if the thrown value had no message).

Common situations: Handler calls an external API that returned an error; null/undefined input access; a DB constraint violation inside the task; downstream service outage.

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/ba158657a028e704. Report an issue: GitHub.