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
- Send only the modern list key: "afterPHIDs": ["PHID-TASK-aaa"] and drop 'afterPHID' entirely.
- Audit payload-merging code (array union / array_merge of defaults with caller input) so the legacy key cannot survive when the list is set.
- 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
- Send only 'beforePHIDs'/'afterPHIDs' lists in new code.
- Never merge legacy single-PHID payloads with modern list payloads.
- Unit-test payload builders to assert legacy keys are absent.
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
- Transaction specifies both "beforePHID" and "beforePHIDs". S
- 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/933a44ca347139a0.
Report an issue: GitHub.