{"record":{"id":"517f1281cd96be51","repo":"phacility/phabricator","slug":"unknown-search-engine-class-s","errorCode":null,"errorMessage":"Unknown search engine class \"%s\".","messagePattern":"Unknown search engine class \"(.+?)\"\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/infrastructure/export/engine/PhabricatorExportEngineBulkJobType.php","lineNumber":56,"sourceCode":"        $actions[] = id(new PhabricatorActionView())\n          ->setHref($file->getDownloadURI())\n          ->setIcon('fa-download')\n          ->setName(pht('Download Data Export'));\n      }\n    }\n\n    return $actions;\n  }\n\n\n  public function runTask(\n    PhabricatorUser $actor,\n    PhabricatorWorkerBulkJob $job,\n    PhabricatorWorkerBulkTask $task) {\n\n    $engine_class = $job->getParameter('engineClass');\n    if (!is_subclass_of($engine_class, 'PhabricatorApplicationSearchEngine')) {\n      throw new Exception(\n        pht(\n          'Unknown search engine class \"%s\".',\n          $engine_class));\n    }\n\n    $engine = newv($engine_class, array())\n      ->setViewer($actor);\n\n    $query_key = $job->getParameter('queryKey');\n    if ($engine->isBuiltinQuery($query_key)) {\n      $saved_query = $engine->buildSavedQueryFromBuiltin($query_key);\n    } else if ($query_key) {\n      $saved_query = id(new PhabricatorSavedQueryQuery())\n        ->setViewer($actor)\n        ->withQueryKeys(array($query_key))\n        ->executeOne();\n    } else {\n      $saved_query = null;","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/infrastructure/export/engine/PhabricatorExportEngineBulkJobType.php#L38-L74","documentation":"Bulk export jobs serialize the search engine class name in the PhabricatorWorkerBulkJob parameters; when a task runs, PhabricatorExportEngineBulkJobType::runTask() re-instantiates it. The guard is_subclass_of($engine_class, 'PhabricatorApplicationSearchEngine') fails when the stored class no longer exists, was renamed, or never was a search engine, so the job cannot proceed and the worker task errors out. This protects against arbitrary class instantiation from job payloads (a mass-assignment/RCE-shaped risk) as well as plain drift.","triggerScenarios":"A user queues 'Export to CSV/Excel' from a query powered by a custom/extension search engine, then the extension is disabled, uninstalled, or renamed before the worker task executes - runTask() gets engineClass 'MyExtensionFancySearchEngine' which is no longer loaded; likewise a hand-crafted/corrupted bulk job row.","commonSituations":"Disabling or upgrading an application right as exports are queued; deploying a refactor that renames engine classes while old bulk jobs linger in the queue; database copies (staging clones of prod) missing the extension code that prod had.","solutions":["Restore the class: re-enable the extension/application that provides the search engine, then let the worker retry the failed tasks (or re-queue them).","If the class is gone for good, cancel the bulk job in Daemons > Bulk Jobs (or via the job's UI) and re-run the export from a query that still exists.","For deployments that rename engine classes, drain or complete queued export jobs before the rename ships."],"exampleFix":"// before: extension disabled, queued job references missing engine\n$engine_class = $job->getParameter('engineClass'); // 'MyExtThingSearchEngine'\nif (!is_subclass_of($engine_class, 'PhabricatorApplicationSearchEngine')) {\n  throw new Exception(...); // worker task fails\n}\n\n// after: keep the class loadable (do not disable the app mid-queue), or pre-check when enqueueing\nif (!is_subclass_of($engine_class, 'PhabricatorApplicationSearchEngine')) {\n  throw new Exception(pht('...')); // validate BEFORE queuing the bulk job\n}","handlingStrategy":"validation","validationCode":"$engine_class = $job->getParameter('engineClass');\nif (!is_subclass_of($engine_class, 'PhabricatorApplicationSearchEngine')) {\n  throw new Exception(pht('Search engine class no longer available: %s', $engine_class));\n}","typeGuard":"function isValidSearchEngineClass($class) {\n  return is_string($class)\n    && class_exists($class)\n    && is_subclass_of($class, 'PhabricatorApplicationSearchEngine');\n}","tryCatchPattern":"try {\n  $this->runTask($actor, $job, $task);\n} catch (Exception $ex) {\n  // permanent failure: do not retry; mark task failed and surface in Bulk Job UI\n  throw new PhabricatorWorkerPermanentFailureException($ex->getMessage());\n}","preventionTips":["Validate engineClass with is_subclass_of() before enqueueing the bulk job, not only in runTask().","Drain or cancel queued export jobs before disabling an application or renaming engine classes.","When cloning prod databases to staging, also deploy the extensions those jobs reference."],"tags":["phabricator","export","bulk-job","worker","class-loading","drift"],"backgroundTag":"stale-class-reference","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}