getgrav/grav · error · RuntimeException
Bad child
Error message
Bad child
What it means
Grav's ACL Action tree enforces strict name nesting: Action::addChild() requires the child action's name to start with the parent's name followed by a dot (parent 'admin' accepts 'admin.stats'). Any structural mismatch — child name not prefixed by the parent name — throws RuntimeException('Bad child'), because the tree could not compute the relative child label otherwise.
Source
Thrown at system/src/Grav/Framework/Acl/Action.php:165
}
/**
* @param string $name
* @return Action|null
*/
public function getChild(string $name): ?Action
{
return $this->children[$name] ?? null;
}
/**
* @param Action $child
* @return void
*/
public function addChild(Action $child): void
{
if (mb_strpos($child->name, "{$this->name}.") !== 0) {
throw new RuntimeException('Bad child');
}
$child->setParent($this);
$name = mb_substr($child->name, mb_strlen($this->name) + 1);
$this->children[$name] = $child;
}
/**
* @return Traversable
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->children);
}
/**
* @return intView on GitHub (pinned to 6040efed04)
Solutions
- Fix the hierarchy: for every addChild() call, ensure child name === parent name + '.' + suffix (e.g. parent 'admin', child 'admin.tools').
- Audit the permissions.yaml of any recently installed/updated plugin: every dotted access label must decompose into a valid chain of segments where each parent label is itself declared.
- Order definitions parent-first when building trees manually (create 'admin' before 'admin.tools').
- If a third-party plugin triggers it, disable that plugin to restore the site and report/patch its permissions file.
Example fix
// before
$admin = new Action('admin');
$admin->addChild(new Action('site.pages')); // Bad child
// after
$admin->addChild(new Action('admin.pages')); // name continues parent's Defensive patterns
Strategy: type-guard
Type guard
function isValidChild(\Grav\Framework\Acl\Action $parent, \Grav\Framework\Acl\Action $child): bool
{
return str_starts_with($child->name, $parent->name . '.');
}
// usage
if (isValidChild($admin, $child)) {
$admin->addChild($child);
} Try / catch
try {
$parent->addChild($child);
} catch (\RuntimeException $e) {
// 'Bad child': log both names to pinpoint the misnested label
error_log(sprintf('ACL misnesting: cannot add %s under %s', $child->name, $parent->name));
throw;
} Prevention
- Derive child names from the parent programmatically: $parent->name . '.' . $suffix.
- Lint plugin permissions.yaml: every dotted label's ancestors must exist in the same file.
- Install third-party plugins on staging first; malformed permission trees take down admin/login.
When it happens
Trigger: Calling $parentAction->addChild($child) where the child name does not begin with "{$parent->name}." — e.g. adding Action 'site.pages' under Action 'admin'; a plugin/theme permissions.yaml whose dotted labels imply nesting that does not match the order/structure PermissionsReader builds; custom code composing Action trees by hand with copy-pasted names.
Common situations: A plugin declares permissions like `admin.super` style labels with inconsistent intermediate segments (child 'admin.a.b.c' added before 'admin.a.b' exists in the expected shape); refactoring permission labels in a plugin but missing one nesting level; third-party plugin with malformed permissions.yaml crashing permission compilation (and thus login/admin) on install.
Related errors
- json_encode(): failed to encode dependencies
- ZipArchiver: ZIP failed to extract {archive_file} to {destin
- Creating directory failed for {filepath}
- Opening file for writing failed on error {$message}
- json_encode(): failed to encode group permissions
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/d40a51d7f1eb0e9b.
Report an issue: GitHub.