{"record":{"id":"2bcceacd170c8628","repo":"BookStackApp/BookStack","slug":"only-top-level-comments-can-be-archived","errorCode":null,"errorMessage":"Only top-level comments can be archived.","messagePattern":"Only top-level comments can be archived\\.","errorType":"exception","errorClass":"NotifyException","httpStatus":400,"severity":"error","filePath":"app/Activity/CommentRepo.php","lineNumber":102,"sourceCode":"    public function update(Comment $comment, string $html): Comment\n    {\n        $comment->updated_by = user()->id;\n        $comment->html = HtmlDescriptionFilter::filterFromString($html);\n        $comment->save();\n\n        ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);\n\n        return $comment;\n    }\n\n\n    /**\n     * Archive an existing comment.\n     */\n    public function archive(Comment $comment, bool $log = true): Comment\n    {\n        if ($comment->parent_id) {\n            throw new NotifyException('Only top-level comments can be archived.', '/', 400);\n        }\n\n        $comment->archived = true;\n        $comment->save();\n\n        if ($log) {\n            ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);\n        }\n\n        return $comment;\n    }\n\n    /**\n     * Un-archive an existing comment.\n     */\n    public function unarchive(Comment $comment, bool $log = true): Comment\n    {\n        if ($comment->parent_id) {","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Activity/CommentRepo.php#L84-L120","documentation":"CommentRepo::archive() refuses to archive a comment that has a parent_id, i.e. a nested reply. In this library only top-level comments (parent_id null) support the archived state; replies are managed implicitly through their parent. It throws NotifyException with HTTP status 400 so the client sees a user-friendly message.","triggerScenarios":"Calling CommentRepo->archive($comment) (or the API/controller endpoint that reaches it) with a Comment model whose parent_id is set — i.e. a reply to another comment.","commonSituations":"Bulk-archiving scripts or moderation tools that iterate over a comment list without filtering out replies; passing a comment id obtained from a nested/reply endpoint; assuming all comments are archivable after a version that introduced archiving for top-level comments only.","solutions":["Check $comment->parent_id is null before calling archive(); skip or handle replies separately.","If the goal is to remove a reply, delete it (or archive its top-level parent) instead of archiving it directly.","Catch NotifyException around the call to surface the 400 message to the user."],"exampleFix":"// before\nforeach ($comments as $comment) {\n    $commentRepo->archive($comment);\n}\n// after\nforeach ($comments as $comment) {\n    if ($comment->parent_id === null) {\n        $commentRepo->archive($comment);\n    }\n}","handlingStrategy":"validation","validationCode":"if ($comment->parent_id !== null) {\n    throw new InvalidArgumentException('Cannot archive a reply; only top-level comments can be archived.');\n}\n$commentRepo->archive($comment);","typeGuard":"function isTopLevelComment($comment): bool {\n    return $comment instanceof Comment && $comment->parent_id === null;\n}","tryCatchPattern":"try {\n    $commentRepo->archive($comment);\n} catch (NotifyException $e) {\n    // handle 400: only top-level comments archivable\n}","preventionTips":["Filter collections to parent_id IS NULL before bulk archive operations.","Centralize archiving in a helper that asserts top-level status.","Treat replies as children: archive the parent only."],"tags":["comments","api","validation","http-400"],"backgroundTag":"invalid-operation-for-nested-resource","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}