phacility/phabricator · error · ConduitException

ERR-BAD-DIFF

ERR-BAD-DIFF

Error message

ERR-BAD-DIFF

What it means

Thrown by differential.createinline when both revisionID and diffID are supplied. The diff is loaded directly (id(new DifferentialDiff())->load($did), a legacy load with no policy filtering) and must both exist and satisfy diff->getRevisionID() == rid. Failing either check throws ConduitException('ERR-BAD-DIFF').

Source

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

    if ($rid) {
      // Given both a revision and a diff, check that they match.
      // Given only a revision, find the active diff.
      $revision = id(new DifferentialRevisionQuery())
        ->setViewer($request->getUser())
        ->withIDs(array($rid))
        ->executeOne();
      if (!$revision) {
        throw new ConduitException('ERR-BAD-REVISION');
      }

      if (!$did) { // did not!
        $diff = $revision->loadActiveDiff();
        $did = $diff->getID();
      } else { // did too!
        $diff = id(new DifferentialDiff())->load($did);
        if (!$diff || $diff->getRevisionID() != $rid) {
          throw new ConduitException('ERR-BAD-DIFF');
        }
      }
    } else if ($did) {
      // Given only a diff, find the parent revision.
      $diff = id(new DifferentialDiff())->load($did);
      if (!$diff) {
        throw new ConduitException('ERR-BAD-DIFF');
      }
      $rid = $diff->getRevisionID();
    } else {
      // Given neither, bail.
      throw new ConduitException('ERR-NEED-DIFF');
    }

    $file = $request->getValue('filePath');
    if (!$file) {
      throw new ConduitException('ERR-NEED-FILE');
    }

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Omit diffID when you pass revisionID - the server then uses the revision's active diff, which cannot mismatch
  2. Otherwise re-fetch the current diff ID for the revision (differential.getdiff with revision_id) before calling
  3. Never cache diff IDs across long-running jobs; refresh them per run
  4. When catching ERR-BAD-DIFF, re-resolve the active diff and retry once

Example fix

// before
$params = array(
  'revisionID' => 789,
  'diffID' => 4210,   // stale: belongs to another revision
  'filePath' => 'src/app.php',
);

// after
$params = array(
  'revisionID' => 789, // server picks the active diff
  'filePath' => 'src/app.php',
);
Defensive patterns

Strategy: validation

Validate before calling

// Prefer sending only revisionID (server uses the active diff).
// If you must pin a diff, verify it belongs to the revision first:
$info = $client->callMethodSynchronous('differential.getdiff',
  array('diff_id' => (int)$did));
if ((int)$info['revision_id'] !== (int)$rid) {
  unset($params['diffID']); // let the server pick the active diff
}

Try / catch

try {
  $client->callMethodSynchronous('differential.createinline', $params);
} catch (ConduitClientException $ex) {
  if ($ex->getErrorCode() === 'ERR-BAD-DIFF') {
    unset($params['diffID']); // retry once with the active diff
    $client->callMethodSynchronous('differential.createinline', $params);
  } else {
    throw $ex;
  }
}

Prevention

When it happens

Trigger: Passing a diffID that does not exist alongside a valid revisionID, or a diffID that belongs to a different revision (stale cached diff ID, off-by-one when parsing arc output, diff from another working copy).

Common situations: Review bots caching diff IDs from earlier runs while the author uploads a new diff; mixing IDs between two revisions edited in parallel; scripts that reuse a diff ID after the revision was updated.

Related errors


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