phacility/phabricator · error · ConduitException

ERR_BAD_DIFF

ERR_BAD_DIFF

Error message

ERR_BAD_DIFF

What it means

differential.updaterevision first loads the diff identified by the diffid parameter. If no visible diff with that ID exists — nonexistent, deleted, or hidden by policy from the viewer — the method throws ERR_BAD_DIFF before it touches the revision.

Source

Thrown at src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php:54

  protected function defineErrorTypes() {
    return array(
      'ERR_BAD_DIFF'     => pht('Bad diff ID.'),
      'ERR_BAD_REVISION' => pht('Bad revision ID.'),
      'ERR_WRONG_USER'   => pht('You are not the author of this revision.'),
      'ERR_CLOSED'       => pht('This revision has already been closed.'),
    );
  }

  protected function execute(ConduitAPIRequest $request) {
    $viewer = $request->getUser();

    $diff = id(new DifferentialDiffQuery())
      ->setViewer($viewer)
      ->withIDs(array($request->getValue('diffid')))
      ->executeOne();
    if (!$diff) {
      throw new ConduitException('ERR_BAD_DIFF');
    }

    $revision = id(new DifferentialRevisionQuery())
      ->setViewer($request->getUser())
      ->withIDs(array($request->getValue('id')))
      ->needReviewers(true)
      ->needActiveDiffs(true)
      ->requireCapabilities(
        array(
          PhabricatorPolicyCapability::CAN_VIEW,
          PhabricatorPolicyCapability::CAN_EDIT,
        ))
      ->executeOne();
    if (!$revision) {
      throw new ConduitException('ERR_BAD_REVISION');
    }

    if ($revision->isPublished()) {

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Send the numeric diffid exactly as returned by differential.creatediff in the same session and under the same user.
  2. Verify the diff exists (differential.diff.search, or load the revision's diffs) before updating.
  3. Ensure the user who created the diff is the user who performs the update.

Example fix

// before
$client->callMethodSynchronous('differential.updaterevision', array(
  'id' => $revision_id,
  'diffid' => $diff_id, // $diff_id may be null or from another instance
));

// after
if ($diff_id === null || $diff_id <= 0) {
  throw new InvalidArgumentException('diffid must be a positive integer diff ID.');
}
$client->callMethodSynchronous('differential.updaterevision', array(
  'id' => $revision_id,
  'diffid' => $diff_id,
));
Defensive patterns

Strategy: validation

Validate before calling

if ($diff_id === null || $diff_id <= 0) {
  throw new InvalidArgumentException(
    'diffid must be a positive integer diff ID.');
}
// prefer: $diff_id taken directly from the creatediff response in this run

Type guard

function isDiffID($value) {
  return is_int($value) && $value > 0;
}

Try / catch

try {
  $result = $client->callMethodSynchronous(
    'differential.updaterevision', $params);
} catch (ConduitClientException $ex) {
  if ($ex->getErrorCode() === 'ERR_BAD_DIFF') {
    // diff missing or invisible: recreate the diff, then retry
  } else {
    throw $ex;
  }
}

Prevention

When it happens

Trigger: diffid is null or unset, refers to a diff on another instance, points at a deleted diff, or the acting user cannot see the diff because of policies.

Common situations: Multi-step arc-style flows where creatediff and updaterevision run under different users or tokens; retrying an update after the diff was removed; IDs copy-pasted between instances.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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