{"record":{"id":"d40a51d7f1eb0e9b","repo":"getgrav/grav","slug":"bad-child","errorCode":null,"errorMessage":"Bad child","messagePattern":"Bad child","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Framework/Acl/Action.php","lineNumber":165,"sourceCode":"    }\n\n    /**\n     * @param string $name\n     * @return Action|null\n     */\n    public function getChild(string $name): ?Action\n    {\n        return $this->children[$name] ?? null;\n    }\n\n    /**\n     * @param Action $child\n     * @return void\n     */\n    public function addChild(Action $child): void\n    {\n        if (mb_strpos($child->name, \"{$this->name}.\") !== 0) {\n            throw new RuntimeException('Bad child');\n        }\n\n        $child->setParent($this);\n        $name = mb_substr($child->name, mb_strlen($this->name) + 1);\n\n        $this->children[$name] = $child;\n    }\n\n    /**\n     * @return Traversable\n     */\n    public function getIterator(): Traversable\n    {\n        return new ArrayIterator($this->children);\n    }\n\n    /**\n     * @return int","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Framework/Acl/Action.php#L147-L183","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n$admin = new Action('admin');\n$admin->addChild(new Action('site.pages'));  // Bad child\n\n// after\n$admin->addChild(new Action('admin.pages')); // name continues parent's","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isValidChild(\\Grav\\Framework\\Acl\\Action $parent, \\Grav\\Framework\\Acl\\Action $child): bool\n{\n    return str_starts_with($child->name, $parent->name . '.');\n}\n\n// usage\nif (isValidChild($admin, $child)) {\n    $admin->addChild($child);\n}","tryCatchPattern":"try {\n    $parent->addChild($child);\n} catch (\\RuntimeException $e) {\n    // 'Bad child': log both names to pinpoint the misnested label\n    error_log(sprintf('ACL misnesting: cannot add %s under %s', $child->name, $parent->name));\n    throw;\n}","preventionTips":["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."],"tags":["grav","acl","permissions","tree-structure"],"backgroundTag":"acl-hierarchy-invalid","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}