Understanding the Error

The error Removed child does not appear to be a tree item occurs in pypdf when you attempt to call remove_from_tree() on a bookmark (outline item) that does not have a properly resolved parent reference in the PDF's internal catalog structure. This is highly common when working with documents loaded via clone_from or PdfReader, as the high-level outline list returned by writer.outline often disconnects from the low-level PDF dictionary tree structure.

However, there is a much simpler and more elegant solution to your problem. You don't actually need to delete and recreate bookmarks to change their colors. While it is a common misconception that pypdf can only add bookmarks, you can actually modify the properties of existing outline items directly in place!

The Solution: Modifying Bookmark Colors In-Place

In the PDF specification, the text color of an outline item is controlled by the /C key, which holds an array of three float values representing RGB colors (e.g., [0.0, 0.0, 0.0] for black, and [0.0, 0.0, 1.0] for blue). Since pypdf's outline items inherit from the dictionary class, you can modify this key directly.

Here is how you can recursively traverse the outline tree and change the color of existing bookmarks from black to blue without deleting anything:

from pypdf import PdfReader, PdfWriter
from pypdf.generic import NameObject, ArrayObject, FloatObject

def change_outline_color(outline, target_color, new_color):
    if isinstance(outline, list):
        # Recursively handle nested/child outline items
        for item in outline:
            change_outline_color(item, target_color, new_color)
    else:
        # Get the color array from the outline item (returns None if not set)
        color = outline.get("/C")
        
        # If color is missing, it defaults to black [0.0, 0.0, 0.0]
        current_color = [float(c) for c in color] if color else [0.0, 0.0, 0.0]
        
        if current_color == target_color:
            # Define the new color as a PDF ArrayObject of FloatObjects
            outline[NameObject("/C")] = ArrayObject([FloatObject(c) for c in new_color])
            print(f"Updated color for: {outline.title}")

# Load the PDF reader and writer
reader = PdfReader("input.pdf")
writer = PdfWriter()
writer.append(reader)

# Target black [0.0, 0.0, 0.0] and change it to blue [0.0, 0.0, 1.0]
change_outline_color(writer.outline, [0.0, 0.0, 0.0], [0.0, 0.0, 1.0])

# Save the modified PDF
with open("output.pdf", "wb") as f:
    writer.write(f)

Why This Approach Works Better

  • Avoids Tree Corruption: Manually deleting nodes from a PDF's outline tree via remove_from_tree() is highly prone to breaking internal document references and causing corrupt PDF structures.
  • Preserves Actions and Destinations: Modifying the /C property directly ensures that all existing links, page destinations, and zoom settings associated with the bookmarks remain perfectly intact.
  • Clean and Fast: This method executes in-memory during the write phase, making it highly performant even for large documents with hundreds of nested bookmarks.