{"record":{"id":"98166d4a04ebf17e","repo":"BookStackApp/BookStack","slug":"only-top-level-comments-can-be-un-archived","errorCode":null,"errorMessage":"Only top-level comments can be un-archived.","messagePattern":"Only top-level comments can be un-archived\\.","errorType":"exception","errorClass":"NotifyException","httpStatus":400,"severity":"error","filePath":"app/Activity/CommentRepo.php","lineNumber":121,"sourceCode":"        }\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) {\n            throw new NotifyException('Only top-level comments can be un-archived.', '/', 400);\n        }\n\n        $comment->archived = false;\n        $comment->save();\n\n        if ($log) {\n            ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);\n        }\n\n        return $comment;\n    }\n\n    /**\n     * Delete a comment from the system.\n     */\n    public function delete(Comment $comment): void\n    {\n        $comment->delete();","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Activity/CommentRepo.php#L103-L139","documentation":"CommentRepo::unarchive() mirrors archive(): it only operates on top-level comments and throws NotifyException (HTTP 400) if the given Comment has a parent_id. Replies inherit visibility from their parent, so they cannot be un-archived individually.","triggerScenarios":"Calling CommentRepo->unarchive($comment) (or its endpoint) with a reply — any Comment model where parent_id is not null.","commonSituations":"Restoring archived content from a backup/export where reply records are processed independently; admin UI or API client that lost track of which ids are top-level; scripts syncing comment state between environments.","solutions":["Verify $comment->parent_id is null before calling unarchive().","Un-archive the top-level parent comment instead of the reply.","Wrap the call in try-catch for NotifyException and report the 400 message."],"exampleFix":"// before\n$comment = Comment::findOrFail($id);\n$commentRepo->unarchive($comment);\n// after\n$comment = Comment::findOrFail($id);\nif ($comment->parent_id !== null) {\n    $comment = $comment->parentComment ?? Comment::findOrFail($comment->parent_id);\n}\n$commentRepo->unarchive($comment);","handlingStrategy":"validation","validationCode":"if ($comment->parent_id !== null) {\n    // resolve to top-level parent instead\n    $comment = Comment::findOrFail($comment->parent_id);\n}\n$commentRepo->unarchive($comment);","typeGuard":"function isTopLevelComment($comment): bool {\n    return $comment instanceof Comment && $comment->parent_id === null;\n}","tryCatchPattern":"try {\n    $commentRepo->unarchive($comment);\n} catch (NotifyException $e) {\n    // handle 400: only top-level comments can be un-archived\n}","preventionTips":["Always resolve a reply to its parent before restore operations.","Track which ids in stored state are top-level comments.","Add integration tests covering reply restore paths."],"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"}