phacility/phabricator · error · Exception
Storage patch "%s" executes in phase "%s", but depends on pa
Error message
Storage patch "%s" executes in phase "%s", but depends on patch "%s" which is in a different phase ("%s"). Patches may not have dependencies across phases. What it means
Storage patches execute in phases ('default' and 'worker') precisely so that a set of patches can complete before daemons restart during an upgrade. A dependency that crosses phases would make the phase split meaningless (the worker-phase patch might run before its default-phase dependency finishes), so the loader forbids it.
Source
Thrown at src/infrastructure/storage/patch/PhabricatorSQLPatchList.php:235
}
}
foreach ($specs as $key => $patch) {
foreach ($patch['after'] as $after) {
if (empty($specs[$after])) {
throw new Exception(
pht(
"Patch '%s' references nonexistent dependency, '%s'. ".
"Patches may only depend on patches which actually exist.",
$key,
$after));
}
$patch_phase = $patch['phase'];
$after_phase = $specs[$after]['phase'];
if ($patch_phase !== $after_phase) {
throw new Exception(
pht(
'Storage patch "%s" executes in phase "%s", but depends on '.
'patch "%s" which is in a different phase ("%s"). Patches '.
'may not have dependencies across phases.',
$key,
$patch_phase,
$after,
$after_phase));
}
}
}
$patches = array();
foreach ($specs as $full_key => $spec) {
$patches[$full_key] = new PhabricatorStoragePatch($spec);
}
// TODO: Detect cycles?View on GitHub (pinned to 5720a38cfe)
Solutions
- Put the dependent patch in the same phase as its dependency (change its 'phase' key or '@phase' header).
- Or remove the cross-phase dependency if the patches are actually independent.
- If a whole chain must run early, move the first patch of the chain and all its dependents to the same phase.
- Re-run 'bin/storage status' to confirm the constraint passes.
Example fix
// before
'20180102.jobs.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20180102.jobs.sql'),
'phase' => 'worker',
'after' => array('20180101.tables.sql'), // default-phase patch
),
// after
'20180102.jobs.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20180102.jobs.sql'),
'after' => array('20180101.tables.sql'),
); Defensive patterns
Strategy: validation
Validate before calling
// Dependencies must not cross phases:
$specs = PhabricatorSQLPatchList::buildAllPatches(); // itself enforces the rule
foreach ($specs as $key => $patch) {
foreach ($patch['after'] as $after) {
if ($patch['phase'] !== $specs[$after]['phase']) {
throw new Exception("{$key} depends on {$after} across phases");
}
}
} Prevention
- When phasing a patch, phase its whole dependency chain or move the chain's head.
- Keep a one-line comment on phased patches naming the phase so future dependencies are added consistently.
When it happens
Trigger: A patch declares 'phase' => 'worker' (or a PHP patch header '// @phase worker') while its 'after' array names a patch in the 'default' phase — or vice versa. The phase comparison happens on the fully assembled global patch map.
Common situations: Moving a patch into the worker phase without moving the patches it builds on; adding a new dependency to an existing phased patch without checking that dependency's phase; forks cherry-picking phased patches onto a different base.
Related errors
- Storage patch "%s" specifies it should apply in phase "%s",
- Patch '%s' is missing key 'after', and is the first patch in
- Patch '%s' references nonexistent dependency, '%s'. Patches
- Storage patch "%s" specifies a "@phase" attribute with no ph
- Storage patch "%s" specifies a "@phase" value ("%s"), but th
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/f8449edafc34b30d.
Report an issue: GitHub.