phacility/phabricator · warning · PhutilArgumentUsageException
Atomizer class '%s' must be a concrete subclass of %s.
Error message
Atomizer class '%s' must be a concrete subclass of %s.
What it means
After `--atomizer <class>` is supplied, the workflow resolves the class with PhutilSymbolLoader using setConcreteOnly(true) and setAncestorClass('DivinerAtomizer'). If the loader selects no symbols — the class does not exist, cannot be loaded, is abstract, or does not extend DivinerAtomizer — this usage exception is thrown. It means the name you gave is not a loadable, concrete DivinerAtomizer subclass.
Source
Thrown at src/applications/diviner/workflow/DivinerAtomizeWorkflow.php:51
$this->readBookConfiguration($args->getArg('book'));
$console = PhutilConsole::getConsole();
$atomizer_class = $args->getArg('atomizer');
if (!$atomizer_class) {
throw new PhutilArgumentUsageException(
pht(
'Specify an atomizer class with %s.',
'--atomizer'));
}
$symbols = id(new PhutilSymbolLoader())
->setName($atomizer_class)
->setConcreteOnly(true)
->setAncestorClass('DivinerAtomizer')
->selectAndLoadSymbols();
if (!$symbols) {
throw new PhutilArgumentUsageException(
pht(
"Atomizer class '%s' must be a concrete subclass of %s.",
$atomizer_class,
'DivinerAtomizer'));
}
$atomizer = newv($atomizer_class, array());
$files = $args->getArg('files');
if (!$files) {
throw new Exception(pht('Specify one or more files to atomize.'));
}
$file_atomizer = new DivinerFileAtomizer();
foreach (array($atomizer, $file_atomizer) as $configure) {
$configure->setBook($this->getConfig('name'));
}View on GitHub (pinned to 5720a38cfe)
Solutions
- Use a shipped atomizer such as DivinerPHPAtomizer and see if the error disappears
- Verify the class exists and is loadable: it must be in a phutil library Diviner can load
- If it is your class, make it `final`/non-abstract and declare `extends DivinerAtomizer`
- Check the exact spelling and case of the class name passed to --atomizer
Example fix
// before
abstract class ProjectAtomizer extends DivinerAtomizer { }
// invoked as --atomizer ProjectAtomizer (abstract -> rejected)
// after
final class ProjectAtomizer extends DivinerAtomizer {
protected function executeAtomize(...) { ... }
} Defensive patterns
Strategy: type-guard
Validate before calling
$atomizer_class = $args->getArg('atomizer');
if (!is_concrete_atomizer_class($atomizer_class)) {
// Fall back to a known-good shipped atomizer.
$atomizer_class = 'DivinerPHPAtomizer';
} Type guard
function is_concrete_atomizer_class($class) {
if (!is_string($class) || !class_exists($class)) {
return false;
}
if (!is_subclass_of($class, 'DivinerAtomizer')) {
return false;
}
return !(new ReflectionClass($class))->isAbstract();
} Prevention
- Validate the class with class_exists/is_subclass_of before passing it to --atomizer
- Keep custom atomizers in a phutil library that Diviner loads
- Add a smoke test that loads every class name your scripts pass to --atomizer
When it happens
Trigger: Passing `--atomizer` with a misspelled class name, an abstract atomizer class, a class that exists but does not extend DivinerAtomizer, or a class in a library not loaded by the Diviner environment (phutil module not in load path).
Common situations: Typos in class names in build scripts; custom atomizers written for an older Diviner API whose parent class moved; forgetting to register the custom atomizer's library with phutil so PhutilSymbolLoader cannot discover it; using a namespaced class name where Diviner expects the old-style global name.
Related errors
- Publisher class '%s' must be a concrete subclass of %s.
- Specify an atomizer class with %s.
- There are no Diviner '%s' files anywhere beneath the current
- Specify a device with --device.
- Specify a private key with --private-key.
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/4f04631f3b625023.
Report an issue: GitHub.