diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php b/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php
index f2632153c3..0f373babf1 100644
--- a/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php
@@ -1,385 +1,385 @@
 <?php
 
 final class HarbormasterBuildUnitMessage
   extends HarbormasterDAO
   implements PhabricatorPolicyInterface {
 
   protected $buildTargetPHID;
   protected $engine;
   protected $namespace;
   protected $name;
   protected $nameIndex;
   protected $result;
   protected $duration;
   protected $properties = array();
 
   private $buildTarget = self::ATTACHABLE;
 
   const FORMAT_TEXT = 'text';
   const FORMAT_REMARKUP = 'remarkup';
 
   public static function initializeNewUnitMessage(
     HarbormasterBuildTarget $build_target) {
     return id(new HarbormasterBuildUnitMessage())
       ->setBuildTargetPHID($build_target->getPHID());
   }
 
   public static function getParameterSpec() {
     return array(
       'name' => array(
         'type' => 'string',
         'description' => pht(
           'Short test name, like "ExampleTest".'),
       ),
       'result' => array(
         'type' => 'string',
         'description' => pht(
           'Result of the test.'),
       ),
       'namespace' => array(
         'type' => 'optional string',
         'description' => pht(
           'Optional namespace for this test. This is organizational and '.
           'is often a class or module name, like "ExampleTestCase".'),
       ),
       'engine' => array(
         'type' => 'optional string',
         'description' => pht(
           'Test engine running the test, like "JavascriptTestEngine". This '.
           'primarily prevents collisions between tests with the same name '.
           'in different test suites (for example, a Javascript test and a '.
           'Python test).'),
       ),
       'duration' => array(
         'type' => 'optional float|int',
         'description' => pht(
           'Runtime duration of the test, in seconds.'),
       ),
       'path' => array(
         'type' => 'optional string',
         'description' => pht(
           'Path to the file where the test is declared, relative to the '.
           'project root.'),
       ),
       'coverage' => array(
         'type' => 'optional map<string, wild>',
         'description' => pht(
           'Coverage information for this test.'),
       ),
       'details' => array(
         'type' => 'optional string',
         'description' => pht(
           'Additional human-readable information about the failure.'),
       ),
       'format' => array(
         'type' => 'optional string',
         'description' => pht(
           'Format for the text provided in "details". Valid values are '.
           '"text" (default) or "remarkup". This controls how test details '.
           'are rendered when shown to users.'),
       ),
     );
   }
 
   public static function newFromDictionary(
     HarbormasterBuildTarget $build_target,
     array $dict) {
 
     $obj = self::initializeNewUnitMessage($build_target);
 
     $spec = self::getParameterSpec();
     $spec = ipull($spec, 'type');
 
     // We're just going to ignore extra keys for now, to make it easier to
     // add stuff here later on.
     $dict = array_select_keys($dict, array_keys($spec));
     PhutilTypeSpec::checkMap($dict, $spec);
 
     $obj->setEngine(idx($dict, 'engine', ''));
     $obj->setNamespace(idx($dict, 'namespace', ''));
     $obj->setName($dict['name']);
     $obj->setResult($dict['result']);
     $obj->setDuration((float)idx($dict, 'duration'));
 
     $path = idx($dict, 'path');
-    if (strlen($path)) {
+    if ($path !== null && strlen($path)) {
       $obj->setProperty('path', $path);
     }
 
     $coverage = idx($dict, 'coverage');
     if ($coverage) {
       $obj->setProperty('coverage', $coverage);
     }
 
     $details = idx($dict, 'details');
     if ($details) {
       $obj->setProperty('details', $details);
     }
 
     $format = idx($dict, 'format');
     if ($format) {
       $obj->setProperty('format', $format);
     }
 
     return $obj;
   }
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_SERIALIZATION => array(
         'properties' => self::SERIALIZATION_JSON,
       ),
       self::CONFIG_COLUMN_SCHEMA => array(
         'engine' => 'text255',
         'namespace' => 'text255',
         'name' => 'text255',
         'nameIndex' => 'bytes12',
         'result' => 'text32',
         'duration' => 'double?',
       ),
       self::CONFIG_KEY_SCHEMA => array(
         'key_target' => array(
           'columns' => array('buildTargetPHID'),
         ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function attachBuildTarget(HarbormasterBuildTarget $build_target) {
     $this->buildTarget = $build_target;
     return $this;
   }
 
   public function getBuildTarget() {
     return $this->assertAttached($this->buildTarget);
   }
 
   public function getProperty($key, $default = null) {
     return idx($this->properties, $key, $default);
   }
 
   public function setProperty($key, $value) {
     $this->properties[$key] = $value;
     return $this;
   }
 
   public function getUnitMessageDetails() {
     return $this->getProperty('details', '');
   }
 
   public function getUnitMessageDetailsFormat() {
     return $this->getProperty('format', self::FORMAT_TEXT);
   }
 
   public function newUnitMessageDetailsView(
     PhabricatorUser $viewer,
     $summarize = false) {
 
     $format = $this->getUnitMessageDetailsFormat();
 
     $is_text = ($format !== self::FORMAT_REMARKUP);
     $is_remarkup = ($format === self::FORMAT_REMARKUP);
     $message = null;
 
     $full_details = $this->getUnitMessageDetails();
     $byte_length = strlen($full_details);
 
     $text_limit = 1024 * 2;
     $remarkup_limit = 1024 * 8;
 
     if (!$byte_length) {
       if ($summarize) {
         return null;
       }
       $message = phutil_tag('em', array(), pht('No details provided.'));
     } else if ($summarize) {
       if ($is_text) {
         $details = id(new PhutilUTF8StringTruncator())
           ->setMaximumBytes($text_limit)
           ->truncateString($full_details);
         $details = phutil_split_lines($details);
 
         $limit = 3;
         if (count($details) > $limit) {
           $details = array_slice($details, 0, $limit);
         }
 
         $details = implode('', $details);
       } else {
         if ($byte_length > $remarkup_limit) {
           $uri = $this->getURI();
 
           if ($uri) {
             $link = phutil_tag(
               'a',
               array(
                 'href' => $uri,
                 'target' => '_blank',
               ),
               pht('View Details'));
           } else {
             $link = null;
           }
 
           $message = array();
           $message[] = phutil_tag(
             'em',
             array(),
             pht('This test has too much data to display inline.'));
           if ($link) {
             $message[] = $link;
           }
 
           $message = phutil_implode_html(" \xC2\xB7 ", $message);
         } else {
           $details = $full_details;
         }
       }
     } else {
       $details = $full_details;
     }
 
     require_celerity_resource('harbormaster-css');
 
     $classes = array();
     $classes[] = 'harbormaster-unit-details';
 
     if ($message !== null) {
       // If we have a message, show that instead of rendering any test details.
       $details = $message;
     } else if ($is_remarkup) {
       $details = new PHUIRemarkupView($viewer, $details);
     } else {
       $classes[] = 'harbormaster-unit-details-text';
       $classes[] = 'PhabricatorMonospaced';
     }
 
     $warning = null;
     if (!$summarize) {
       $warnings = array();
 
       if ($is_remarkup && ($byte_length > $remarkup_limit)) {
         $warnings[] = pht(
           'This test result has %s bytes of Remarkup test details. Remarkup '.
           'blocks longer than %s bytes are not rendered inline when showing '.
           'test summaries.',
           new PhutilNumber($byte_length),
           new PhutilNumber($remarkup_limit));
       }
 
       if ($warnings) {
         $warning = id(new PHUIInfoView())
           ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
           ->setErrors($warnings);
       }
     }
 
     $content = phutil_tag(
       'div',
       array(
         'class' => implode(' ', $classes),
       ),
       $details);
 
     return array(
       $warning,
       $content,
     );
   }
 
   public function getUnitMessageDisplayName() {
     $name = $this->getName();
 
     $namespace = $this->getNamespace();
     if (strlen($namespace)) {
       $name = $namespace.'::'.$name;
     }
 
     $engine = $this->getEngine();
     if (strlen($engine)) {
       $name = $engine.' > '.$name;
     }
 
     if (!strlen($name)) {
       return pht('Nameless Test (%d)', $this->getID());
     }
 
     return $name;
   }
 
   public function getSortKey() {
     $status = $this->getResult();
     $sort = HarbormasterUnitStatus::getUnitStatusSort($status);
 
     $parts = array(
       $sort,
       $this->getEngine(),
       $this->getNamespace(),
       $this->getName(),
       $this->getID(),
     );
 
     return implode("\0", $parts);
   }
 
   public function getURI() {
     $id = $this->getID();
 
     if (!$id) {
       return null;
     }
 
     return urisprintf(
       '/harbormaster/unit/view/%d/',
       $id);
   }
 
   public function save() {
     if ($this->nameIndex === null) {
       $this->nameIndex = HarbormasterString::newIndex($this->getName());
     }
 
     // See T13088. While we're letting installs do online migrations to avoid
     // downtime, don't populate the "name" column for new writes. New writes
     // use the "HarbormasterString" table instead.
     $old_name = $this->name;
     $this->name = '';
 
     $caught = null;
     try {
       $result = parent::save();
     } catch (Exception $ex) {
       $caught = $ex;
     }
 
     $this->name = $old_name;
 
     if ($caught) {
       throw $caught;
     }
 
     return $result;
   }
 
 
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
     );
   }
 
   public function getPolicy($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return PhabricatorPolicies::getMostOpenPolicy();
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     return false;
   }
 
 }