diff --git a/djblets/extensions/hooks.py b/djblets/extensions/hooks.py
index 772c307fd2372e44b34c20eb80a9309cfc29066a..d14fd0324a635ddeeea6f89707ae9bf601355655 100644
--- a/djblets/extensions/hooks.py
+++ b/djblets/extensions/hooks.py
@@ -208,15 +208,11 @@ class TemplateHook(AppliesToURLMixin, ExtensionHook):
         By default, this renders the provided template name to a string
         and returns it.
         """
-        context.push()
         context['extension'] = self.extension
         context.update(self.get_extra_context(request, context))
         context.update(self.extra_context)
 
-        try:
-            return render_to_string(self.template_name, context)
-        finally:
-            context.pop()
+        return render_to_string(self.template_name, context)
 
     def get_extra_context(self, request, context):
         """Returns extra context for the hook.
diff --git a/djblets/extensions/templatetags/djblets_extensions.py b/djblets/extensions/templatetags/djblets_extensions.py
index 57e67e3a9b04c10997fade36e3c773ef00d52f00..cea6f82b1131d2edf005c11c504fd837614c1733 100644
--- a/djblets/extensions/templatetags/djblets_extensions.py
+++ b/djblets/extensions/templatetags/djblets_extensions.py
@@ -19,17 +19,23 @@ register = template.Library()
 @register.tag
 @basictag(takes_context=True)
 def template_hook_point(context, name):
-    """
-    Registers a template hook point that TemplateHook instances can
-    attach to.
-    """
-    request = context['request']
+    """Registers a place where TemplateHooks can render to."""
+    def _render_hooks():
+        request = context['request']
+
+        for hook in TemplateHook.by_name(name):
+            if hook.applies_to(request):
+                context.push()
+
+                try:
+                    yield hook.render_to_string(request, context)
+                except Exception as e:
+                    logging.error('Error rendering TemplateHook %r: %s',
+                                  hook, e, exc_info=1)
+
+                context.pop()
 
-    return ''.join([
-        hook.render_to_string(request, context)
-        for hook in TemplateHook.by_name(name)
-        if hook.applies_to(request)
-    ])
+    return ''.join(_render_hooks())
 
 
 @register.tag
diff --git a/djblets/extensions/tests.py b/djblets/extensions/tests.py
index 2f3a5ecdad4e8905cdb7217f0f232d399790fd53..fedda87d44016d06828f4a9f36bdc53356b2008b 100644
--- a/djblets/extensions/tests.py
+++ b/djblets/extensions/tests.py
@@ -31,6 +31,7 @@ from django.conf import settings
 from django.conf.urls import include, patterns
 from django.core.exceptions import ImproperlyConfigured
 from django.dispatch import Signal
+from django.template import Context, Template
 from django.utils import six
 from kgb import SpyAgency
 from mock import Mock
@@ -767,5 +768,42 @@ class TemplateHookTest(TestCase):
         self.assertTrue(
             self.template_hook_with_applies.applies_to(self.request))
 
+    def test_context_doesnt_leak(self):
+        """Testing TemplateHook's context won't leak state"""
+        class MyTemplateHook(TemplateHook):
+            def render_to_string(self, request, context):
+                context['leaky'] = True
+
+                return ''
+
+        hook = MyTemplateHook(self.extension, 'test')
+        context = Context({})
+        context['request'] = None
+
+        t = Template(
+            '{% load djblets_extensions %}'
+            '{% template_hook_point "test" %}')
+        t.render(context).strip()
+
+        self.assertNotIn('leaky', context)
+
+    def test_sandbox(self):
+        """Testing TemplateHook sandboxing"""
+        class MyTemplateHook(TemplateHook):
+            def render_to_string(self, request, context):
+                raise Exception('Oh noes')
+
+        hook = MyTemplateHook(self.extension, 'test')
+        context = Context({})
+        context['request'] = None
+
+        t = Template(
+            '{% load djblets_extensions %}'
+            '{% template_hook_point "test" %}')
+        t.render(context).strip()
+
+        # Didn't crash. We're good.
+
+
 # A dummy function that acts as a View method
 test_view_method = Mock()
