BookStackApp/BookStack · error · NotifyException

Only top-level comments can be un-archived.

Error message

Only top-level comments can be un-archived.

What it means

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.

Source

Thrown at app/Activity/CommentRepo.php:121

        }

        $comment->archived = true;
        $comment->save();

        if ($log) {
            ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);
        }

        return $comment;
    }

    /**
     * Un-archive an existing comment.
     */
    public function unarchive(Comment $comment, bool $log = true): Comment
    {
        if ($comment->parent_id) {
            throw new NotifyException('Only top-level comments can be un-archived.', '/', 400);
        }

        $comment->archived = false;
        $comment->save();

        if ($log) {
            ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);
        }

        return $comment;
    }

    /**
     * Delete a comment from the system.
     */
    public function delete(Comment $comment): void
    {
        $comment->delete();

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Verify $comment->parent_id is null before calling unarchive().
  2. Un-archive the top-level parent comment instead of the reply.
  3. Wrap the call in try-catch for NotifyException and report the 400 message.

Example fix

// before
$comment = Comment::findOrFail($id);
$commentRepo->unarchive($comment);
// after
$comment = Comment::findOrFail($id);
if ($comment->parent_id !== null) {
    $comment = $comment->parentComment ?? Comment::findOrFail($comment->parent_id);
}
$commentRepo->unarchive($comment);
Defensive patterns

Strategy: validation

Validate before calling

if ($comment->parent_id !== null) {
    // resolve to top-level parent instead
    $comment = Comment::findOrFail($comment->parent_id);
}
$commentRepo->unarchive($comment);

Type guard

function isTopLevelComment($comment): bool {
    return $comment instanceof Comment && $comment->parent_id === null;
}

Try / catch

try {
    $commentRepo->unarchive($comment);
} catch (NotifyException $e) {
    // handle 400: only top-level comments can be un-archived
}

Prevention

When it happens

Trigger: Calling CommentRepo->unarchive($comment) (or its endpoint) with a reply — any Comment model where parent_id is not null.

Common situations: 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.

Related errors


AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02). Data as JSON: /api/errors/98166d4a04ebf17e. Report an issue: GitHub.