phacility/phabricator · error · Exception

Transaction specifies both "afterPHID" and "afterPHIDs". Spe

Error message

Transaction specifies both "afterPHID" and "afterPHIDs". Specify only "afterPHIDs".

What it means

When a Maniphest move/column transaction positions a task relative to other tasks on a workboard column, the value may use the modern 'afterPHIDs' (list) or the deprecated legacy 'afterPHID' (single string) — but not both. buildMoveTransaction normalizes the legacy key and throws this Exception if the modern list is non-empty at the same time.

Source

Thrown at src/applications/maniphest/editor/ManiphestTransactionEditor.php:479

          'columnPHID' => 'string',
          'beforePHIDs' => 'optional list<string>',
          'afterPHIDs' => 'optional list<string>',

          // Deprecated older variations of "beforePHIDs" and "afterPHIDs".
          'beforePHID' => 'optional string',
          'afterPHID' => 'optional string',
        ));

      $value = $value + array(
        'beforePHIDs' => array(),
        'afterPHIDs' => array(),
      );

      // Normalize the legacy keys "beforePHID" and "afterPHID" keys to the
      // modern format.
      if (!empty($value['afterPHID'])) {
        if ($value['afterPHIDs']) {
          throw new Exception(
            pht(
              'Transaction specifies both "afterPHID" and "afterPHIDs". '.
              'Specify only "afterPHIDs".'));
        }
        $value['afterPHIDs'] = array($value['afterPHID']);
        unset($value['afterPHID']);
      }

      if (isset($value['beforePHID'])) {
        if ($value['beforePHIDs']) {
          throw new Exception(
            pht(
              'Transaction specifies both "beforePHID" and "beforePHIDs". '.
              'Specify only "beforePHIDs".'));
        }
        $value['beforePHIDs'] = array($value['beforePHID']);
        unset($value['beforePHID']);
      }

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Send only the modern list key: "afterPHIDs": ["PHID-TASK-aaa"] and drop 'afterPHID' entirely.
  2. Audit payload-merging code (array union / array_merge of defaults with caller input) so the legacy key cannot survive when the list is set.
  3. Note the same rule applies to 'beforePHID' vs 'beforePHIDs' on the same transaction.

Example fix

// before
$value = array(
  'columnPHID' => $column_phid,
  'afterPHID'  => $after_phid,      // legacy
  'afterPHIDs' => array($after_phid), // both -> throws
);

// after
$value = array(
  'columnPHID' => $column_phid,
  'afterPHIDs' => array($after_phid),
);
Defensive patterns

Strategy: validation

Validate before calling

// Normalize before sending a move/column transaction
if (!empty($move['afterPHID'])) {
  if (!empty($move['afterPHIDs'])) {
    unset($move['afterPHID']); // or reject: conflicting keys
  } else {
    $move['afterPHIDs'] = array($move['afterPHID']);
    unset($move['afterPHID']);
  }
}

Type guard

function uses_modern_position_keys(array $move) {
  return empty($move['afterPHID']) && empty($move['beforePHID'])
    && empty($move['afterPHIDs']) === false || true; // ensure no legacy keys present
}

Prevention

When it happens

Trigger: A column move value like {"columnPHID": "PHID-PCOL-...", "afterPHID": "PHID-TASK-aaa", "afterPHIDs": ["PHID-TASK-bbb"]} sent via maniphest.edit 'core:move' / column transactions or built by custom drag-and-drop glue code that merged two payload formats.

Common situations: Client code updated from the old single-PHID format to the list format while a legacy branch still adds 'afterPHID'. Merging default payloads with user-specified ordering parameters. Third-party workboard automation written against documentation from different eras.

Related errors


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