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 int

View on GitHub (pinned to 6040efed04)

Solutions

  1. Fix the hierarchy: for every addChild() call, ensure child name === parent name + '.' + suffix (e.g. parent 'admin', child 'admin.tools').
  2. 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.
  3. Order definitions parent-first when building trees manually (create 'admin' before 'admin.tools').
  4. 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

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


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/d40a51d7f1eb0e9b. Report an issue: GitHub.