phacility/phabricator · error · Exception
Transaction specifies both "beforePHID" and "beforePHIDs". S
Error message
Transaction specifies both "beforePHID" and "beforePHIDs". Specify only "beforePHIDs".
What it means
The mirror of the 'after' rule: a column move transaction may use the modern 'beforePHIDs' list or the deprecated 'beforePHID' single string, never both. During normalization in buildMoveTransaction, a non-empty 'beforePHIDs' alongside an isset 'beforePHID' throws this Exception.
Source
Thrown at src/applications/maniphest/editor/ManiphestTransactionEditor.php:490
'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']);
}
foreach ($value['beforePHIDs'] as $phid) {
$relative_phids[] = $phid;
}
foreach ($value['afterPHIDs'] as $phid) {
$relative_phids[] = $phid;
}
$new[$key] = $value;
}View on GitHub (pinned to 5720a38cfe)
Solutions
- Use only "beforePHIDs": ["PHID-TASK-aaa"] and remove 'beforePHID' from the payload.
- Make legacy-key normalization unconditional in your client: if you populate 'beforePHID', never also send 'beforePHIDs'.
- Remember the parallel 'afterPHID'/'afterPHIDs' rule — fixing one side alone still leaves the transaction able to throw.
Example fix
// before $value = array( 'columnPHID' => $column_phid, 'beforePHID' => $before_phid, 'beforePHIDs' => array($before_phid), ); // after $value = array( 'columnPHID' => $column_phid, 'beforePHIDs' => array($before_phid), );
Defensive patterns
Strategy: validation
Validate before calling
if (isset($move['beforePHID'])) {
if (!empty($move['beforePHIDs'])) {
unset($move['beforePHID']); // conflicting formats; keep the list
} else {
$move['beforePHIDs'] = array($move['beforePHID']);
unset($move['beforePHID']);
}
} Type guard
function has_no_legacy_position_keys(array $move) {
return !isset($move['beforePHID']) && !isset($move['afterPHID']);
} Prevention
- Use only 'beforePHIDs'/'afterPHIDs'; drop the singular legacy keys.
- Guard default-value merging so legacy keys cannot coexist with lists.
- Fix both before- and after-variants together when refactoring.
When it happens
Trigger: A move value like {"columnPHID": "PHID-PCOL-...", "beforePHID": "PHID-TASK-aaa", "beforePHIDs": ["PHID-TASK-bbb"]} — typically from code that sets the legacy key first and then unconditionally initializes the list key.
Common situations: Refactoring old single-relative-position code to multi-position and leaving the old assignment on a code path that also populates the list. Default-value builders ($value += array('beforePHIDs' => ...)) combined with caller-supplied 'beforePHID'. Same-era documentation mixing both key styles.
Related errors
- Transaction specifies both "afterPHID" and "afterPHIDs". Spe
- Column move transaction specifies column PHID "%s", but ther
- When moving objects between columns on a board, columns must
- ERR_BAD_TASK
- Parameter "fullText" is no longer supported. Use method "man
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/08c2442798aefdb5.
Report an issue: GitHub.