phacility/phabricator · error · ConduitException

ERR-BAD-FILE

ERR-BAD-FILE

Error message

ERR-BAD-FILE

What it means

Thrown by differential.createinline after the diff's changesets are loaded (DifferentialChangeset where diffID matches). The method compares filePath to each changeset's getFilename() with exact equality (last match wins) and records the matching changeset ID. If no changeset filename equals filePath, it throws ConduitException('ERR-BAD-FILE').

Source

Thrown at src/applications/differential/conduit/DifferentialCreateInlineConduitAPIMethod.php:95

      // Given neither, bail.
      throw new ConduitException('ERR-NEED-DIFF');
    }

    $file = $request->getValue('filePath');
    if (!$file) {
      throw new ConduitException('ERR-NEED-FILE');
    }
    $changes = id(new DifferentialChangeset())->loadAllWhere(
      'diffID = %d',
      $did);
    $cid = null;
    foreach ($changes as $id => $change) {
      if ($file == $change->getFilename()) {
        $cid = $id;
      }
    }
    if ($cid == null) {
      throw new ConduitException('ERR-BAD-FILE');
    }

    $inline = id(new DifferentialInlineComment())
      ->setRevisionID($rid)
      ->setChangesetID($cid)
      ->setAuthorPHID($request->getUser()->getPHID())
      ->setContent($request->getValue('content'))
      ->setIsNewFile((int)$request->getValue('isNewFile'))
      ->setLineNumber($request->getValue('lineNumber'))
      ->setLineLength($request->getValue('lineLength', 0))
      ->save();

    // Load everything again, just to be safe.
    $changeset = id(new DifferentialChangeset())
      ->load($inline->getChangesetID());
    return $this->buildInlineInfoDictionary($inline, $changeset);
  }

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Fetch the exact filename list first (differential.getcommitpaths for the revision, or the changesets from differential.getdiff) and send a value from that list
  2. Strip leading slashes, './' segments, and convert separators to match the diff's relative paths
  3. When commenting on a specific diff, take the path from that same diff's changesets, not from the working copy
  4. If the file is not in the diff, attach the note as a plain comment instead of an inline

Example fix

// before
$params = array(
  'revisionID' => 789,
  'filePath' => '/home/ci/repo/src/app.php',
);

// after
$params = array(
  'revisionID' => 789,
  'filePath' => 'src/app.php', // exact match against the changeset filename
);
Defensive patterns

Strategy: validation

Validate before calling

// Match exactly against the diff's own changeset filenames.
$paths = $client->callMethodSynchronous('differential.getcommitpaths',
  array('revision_id' => (int)$rid));
if (!in_array($params['filePath'], $paths, true)) {
  $params['filePath'] = pickClosest($paths, $params['filePath']); // or skip
}

Try / catch

try {
  $client->callMethodSynchronous('differential.createinline', $params);
} catch (ConduitClientException $ex) {
  if ($ex->getErrorCode() === 'ERR-BAD-FILE') {
    // path not in this diff: post a revision-level comment instead
  } else {
    throw $ex;
  }
}

Prevention

When it happens

Trigger: Passing a path that does not exactly equal a changeset filename: absolute paths where the diff stores relative ones, a leading './' or '/', Windows backslash separators, a renamed file's old name, or a file that is simply not touched by that diff.

Common situations: Lint bots reporting paths from a different working-copy root; comments targeted at a file changed only in a newer diff than the one being commented on; tools normalizing paths differently than git/arc did when the diff was uploaded.

Related errors


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