# Buggy code
def process_todo_nodes(app, doctree, fromdocname):
    # type: (Sphinx, nodes.Node, unicode) -> None
    if not app.config["todo_include_todos"]:
        for node in doctree.traverse(todo_node):
            node.parent.remove(node)

    # Replace all todolist nodes with a list of the collected todos.
    # Augment each todo with a backlink to the original location.
    env = app.builder.env

    if not hasattr(env, "todo_all_todos"):
        env.todo_all_todos = []  # type: ignore

    for node in doctree.traverse(todolist):
        if node.get("ids"):
            content = [nodes.target()]
        else:
            content = []

        if not app.config["todo_include_todos"]:
            node.replace_self(content)
            continue

        for todo_info in env.todo_all_todos:  # type: ignore
            para = nodes.paragraph(classes=["todo-source"])
            if app.config["todo_link_only"]:
                description = _("<<original entry>>")
            else:
                description = _(
                    "(The <<original entry>> is located in %s, line %d.)"
                ) % (todo_info["source"], todo_info["lineno"])
            desc1 = description[: description.find("<<")]
            desc2 = description[description.find(">>") + 2 :]
            para += nodes.Text(desc1, desc1)

            # Create a reference
            newnode = nodes.reference("", "", internal=True)
            innernode = nodes.emphasis(_("original entry"), _("original entry"))
            try:
                newnode["refuri"] = app.builder.get_relative_uri(
                    fromdocname, todo_info["docname"]
                )
                newnode["refuri"] += "#" + todo_info["target"]["refid"]
            except NoUri:
                # ignore if no URI can be determined, e.g. for LaTeX output
                pass
            newnode.append(innernode)
            para += newnode
            para += nodes.Text(desc2, desc2)

            todo_entry = todo_info["todo"]
            # Remove targetref from the (copied) node to avoid emitting a
            # duplicate label of the original entry when we walk this node.
            del todo_entry["targetref"]

            # (Recursively) resolve references in the todo content
            env.resolve_references(todo_entry, todo_info["docname"], app.builder)

            # Insert into the todolist
            content.append(todo_entry)
            content.append(para)

        node.replace_self(content)


# Traceback (KeyError)
# Sphinx version: 1.6.5
# Python version: 2.7.10 (CPython)
# Docutils version: 0.14-
# Jinja2 version: 2.9.6
# Last messages:
#   writing output... [ 92%] reverse-dict/writing/indexing
#   writing output... [ 92%] reverse-dict/writing/link
#   writing output... [ 93%] reverse-dict/writing/note
#   writing output... [ 93%] reverse-dict/writing/numbering
#   writing output... [ 94%] reverse-dict/writing/only
#   writing output... [ 94%] reverse-dict/writing/refer
#   writing output... [ 95%] reverse-dict/writing/replace
#   writing output... [ 96%] reverse-dict/writing/strike
#   writing output... [ 96%] reverse-dict/writing/today
#   writing output... [ 97%] reverse-dict/writing/todo
# Loaded extensions:
#   japanesesupport (unknown version) from /Users/shimizukawa/py/sphinxjp/sphinxjp-web/source/japanesesupport.pyc
#   sphinx.ext.todo (1.6.5) from /Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/sphinx/ext/todo.pyc
#   alabaster (0.7.10) from /Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/alabaster/__init__.pyc
#   sphinxcontrib.gist (unknown version) from /Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/sphinxcontrib/gist/__init__.pyc
Traceback (most recent call last):
File "/Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/sphinx/cmdline.py", line 306, in main
app.build(opts.force_all, filenames)
File "/Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/sphinx/application.py", line 339, in build
self.builder.build_update()
File "/Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/sphinx/builders/__init__.py", line 331, in build_update
'out of date' % len(to_build))
File "/Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/sphinx/builders/__init__.py", line 397, in build
self.write(docnames, list(updated_docnames), method)
File "/Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/sphinx/builders/__init__.py", line 434, in write
self._write_serial(sorted(docnames))
File "/Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/sphinx/builders/__init__.py", line 441, in _write_serial
doctree = self.env.get_and_resolve_doctree(docname, self)
File "/Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/sphinx/environment/__init__.py", line 911, in get_and_resolve_doctree
self.apply_post_transforms(doctree, docname)
File "/Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/sphinx/environment/__init__.py", line 963, in apply_post_transforms
self.app.emit('doctree-resolved', doctree, docname)
File "/Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/sphinx/application.py", line 489, in emit
return self.events.emit(event, self, *args)
File "/Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/sphinx/events.py", line 79, in emit
results.append(callback(*args))
File "/Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/sphinx/ext/todo.py", line 181, in process_todo_nodes
del todo_entry['targetref']
File "/Users/shimizukawa/py/sphinxjp/sphinxjp-web/.venv/lib/python2.7/site-packages/docutils/nodes.py", line 594, in __delitem__
del self.attributes[key]
KeyError: 'targetref'

# Fixed code
def process_todo_nodes(app, doctree, fromdocname):
    # type: (Sphinx, nodes.Node, unicode) -> None
    if not app.config["todo_include_todos"]:
        for node in doctree.traverse(todo_node):
            node.parent.remove(node)

    # Replace all todolist nodes with a list of the collected todos.
    # Augment each todo with a backlink to the original location.
    env = app.builder.env

    if not hasattr(env, "todo_all_todos"):
        env.todo_all_todos = []  # type: ignore

    for node in doctree.traverse(todolist):
        if node.get("ids"):
            content = [nodes.target()]
        else:
            content = []

        if not app.config["todo_include_todos"]:
            node.replace_self(content)
            continue

        for todo_info in env.todo_all_todos:  # type: ignore
            para = nodes.paragraph(classes=["todo-source"])
            if app.config["todo_link_only"]:
                description = _("<<original entry>>")
            else:
                description = _(
                    "(The <<original entry>> is located in %s, line %d.)"
                ) % (todo_info["source"], todo_info["lineno"])
            desc1 = description[: description.find("<<")]
            desc2 = description[description.find(">>") + 2 :]
            para += nodes.Text(desc1, desc1)

            # Create a reference
            newnode = nodes.reference("", "", internal=True)
            innernode = nodes.emphasis(_("original entry"), _("original entry"))
            try:
                newnode["refuri"] = app.builder.get_relative_uri(
                    fromdocname, todo_info["docname"]
                )
                newnode["refuri"] += "#" + todo_info["target"]["refid"]
            except NoUri:
                # ignore if no URI can be determined, e.g. for LaTeX output
                pass
            newnode.append(innernode)
            para += newnode
            para += nodes.Text(desc2, desc2)

            todo_entry = todo_info["todo"]
            # Remove targetref from the (copied) node to avoid emitting a
            # duplicate label of the original entry when we walk this node.
            if "targetref" in todo_entry:
                del todo_entry["targetref"]

            # (Recursively) resolve references in the todo content
            env.resolve_references(todo_entry, todo_info["docname"], app.builder)

            # Insert into the todolist
            content.append(todo_entry)
            content.append(para)

        node.replace_self(content)