phacility/phabricator · error · PhabricatorWorkerPermanentFailureException

Expected task data to be a dictionary.

Error message

Expected task data to be a dictionary.

What it means

Worker task data is whatever was passed to PhabricatorWorker::scheduleTask(); getTaskDataValue() assumes it is an associative array and throws PhabricatorWorkerPermanentFailureException when it is not. Permanent failure marks the task as garbage instead of retrying it. In practice the scheduler passed a JSON string (or a scalar) where a plain PHP array was expected.

Source

Thrown at src/infrastructure/daemon/workers/PhabricatorWorker.php:117

      return null;
    }
    return $task->getID();
  }

  abstract protected function doWork();

  final public function __construct($data) {
    $this->data = $data;
  }

  final protected function getTaskData() {
    return $this->data;
  }

  final protected function getTaskDataValue($key, $default = null) {
    $data = $this->getTaskData();
    if (!is_array($data)) {
      throw new PhabricatorWorkerPermanentFailureException(
        pht('Expected task data to be a dictionary.'));
    }
    return idx($data, $key, $default);
  }

  final public function executeTask() {
    $this->doWork();
  }

  final public static function scheduleTask(
    $task_class,
    $data,
    $options = array()) {

    PhutilTypeSpec::checkMap(
      $options,
      array(
        'priority' => 'optional int|null',

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Change the scheduler to pass a plain PHP array to scheduleTask().
  2. If payloads may arrive pre-encoded, decode in the worker before reading keys: $data = $this->getTaskData(); if (is_string($data)) { $data = phutil_json_decode($data); }.
  3. Inspect the failed task's stored data to confirm what was actually queued.

Example fix

// before
PhabricatorWorker::scheduleTask(
  'MyWorker',
  json_encode(array('phid' => $object_phid)));

// after
PhabricatorWorker::scheduleTask(
  'MyWorker',
  array('phid' => $object_phid));
Defensive patterns

Strategy: type-guard

Validate before calling

if (!is_array($task_data)) {
  throw new InvalidArgumentException('Worker task data must be an array.');
}
PhabricatorWorker::scheduleTask($worker_class, $task_data);

Type guard

function is_dictionary_task_data($data) {
  return is_array($data);
}

Try / catch

try {
  $value = $this->getTaskDataValue('key');
} catch (PhabricatorWorkerPermanentFailureException $ex) {
  // permanent: log the task as garbage; do not requeue blindly
}

Prevention

When it happens

Trigger: Calling PhabricatorWorker::scheduleTask('MyWorker', json_encode(array(...))) and then having MyWorker call getTaskDataValue(); scheduling with null or integer payloads; legacy task rows queued before a data-format convention was adopted.

Common situations: New worker code that 'helpfully' pre-encodes the payload to JSON; external systems inserting raw string payloads into the worker table; refactors that changed what scheduleTask receives.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/0485f583bcfb0cae. Report an issue: GitHub.