The harbormaster target logic is this:
```
try {
$status_pending = HarbormasterBuildTarget::STATUS_PENDING;
if ($target->getTargetStatus() == $status_pending) {
$target->setTargetStatus(HarbormasterBuildTarget::STATUS_BUILDING);
$target->save();
}
$implementation = $target->getImplementation();
$implementation->execute($build, $target);
$next_status = HarbormasterBuildTarget::STATUS_PASSED;
if ($implementation->shouldWaitForMessage($target)) {
$next_status = HarbormasterBuildTarget::STATUS_WAITING;
}
$target->setTargetStatus($next_status);
$target->save();
} catch (PhabricatorWorkerYieldException $ex) {
// If the target wants to yield, let that escape without further
// processing. We'll resume after the task retries.
throw $ex;
} catch (Exception $ex) {
phlog($ex);
try {
$log = $build->createLog($target, 'core', 'exception');
$start = $log->start();
$log->append((string)$ex);
$log->finalize($start);
} catch (Exception $log_ex) {
phlog($log_ex);
}
$target->setTargetStatus(HarbormasterBuildTarget::STATUS_FAILED);
$target->save();
}
```
Thus the only way to mark a target as failed is to throw an exception.
Marking the build as failed like this:
```
$build->setBuildStatus(HarbormasterBuild::STATUS_FAILED);
```
doesn't work either, because the Harbormaster build engine updates the build status based on the targets.