Skip to content

Failed job providers crash on corrupted payload, losing the failure record #59635

@ruttydm

Description

@ruttydm

Summary

Three UUID-based failed job providers use json_decode($payload, true)['uuid'] without checking if json_decode returned null. If the queue backend delivers a corrupted payload, the provider crashes, and the failed job record is permanently lost — the safety net has a hole.

Version: 13.3.0

Affected files

1. FileFailedJobProvider.php line 59:

$id = json_decode($payload, true)['uuid'];

2. DatabaseUuidFailedJobProvider.php line 58:

'uuid' => $uuid = json_decode($payload, true)['uuid'],

3. DynamoDbFailedJobProvider.php line 60:

$id = json_decode($payload, true)['uuid'];

The non-UUID DatabaseFailedJobProvider is not affected — it stores the raw payload without parsing.

How it fails

If json_decode() returns null (corrupted/truncated payload):

  1. null['uuid'] emits a warning and returns null in PHP 8+
  2. DatabaseUuidFailedJobProvider: inserts null into UUID column → likely constraint violation → exception
  3. DynamoDbFailedJobProvider: passes null as DynamoDB 'S' attribute → AWS SDK throws
  4. FileFailedJobProvider: stores null as job ID → corrupts the failed jobs file

In all cases, the exception propagates through WorkCommand::logFailedJob() (which has no try/catch around the log() call), and the failed job record is never written.

When it triggers

The $payload comes from $event->job->getRawBody() — raw bytes from the queue backend. While Laravel always creates valid JSON payloads internally, the data could be corrupted by:

  • Redis network issues during write/read
  • Database row corruption
  • SQS message body truncation
  • Custom queue drivers with payload bugs

Impact

The failed jobs table is the safety net for jobs that fall through the cracks. When the safety net itself crashes, you lose both the job execution AND the audit trail that it failed. The job enters a silent limbo — no retry button, no failure record, no alert.

Suggested fix

$data = json_decode($payload, true);
$id = $data['uuid'] ?? null;

Or more defensively:

$data = json_decode($payload, true);
$id = is_array($data) ? ($data['uuid'] ?? 'unknown-' . Str::uuid()) : 'corrupt-' . Str::uuid();

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions