From 6475f86c0d92437159daf972a87a1a1803d98f9f Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 1 Dec 2025 14:29:52 +0530 Subject: [PATCH 1/5] feat: Add dynamic skip rules for kernel modules to debugpy configuration. --- ipykernel/debugger.py | 8 +++++++- ipykernel/ipkernel.py | 7 +++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index cfa976107..7a158ba5b 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -326,7 +326,7 @@ class Debugger: ] def __init__( - self, log, debugpy_stream, event_callback, shell_socket, session, just_my_code=True + self, log, debugpy_stream, event_callback, shell_socket, session, kernel_modules, just_my_code=True ): """Initialize the debugger.""" self.log = log @@ -335,6 +335,7 @@ def __init__( self.session = session self.is_started = False self.event_callback = event_callback + self.kernel_modules = kernel_modules self.just_my_code = just_my_code self.stopped_queue: Queue[t.Any] = Queue() @@ -574,6 +575,11 @@ async def attach(self, message): # Set debugOptions for breakpoints in python standard library source. if not self.just_my_code: message["arguments"]["debugOptions"] = ["DebugStdLib"] + + # Dynamic skip rules (computed at kernel startup) + rules = [{"path": path, "include": False} for path in self.kernel_modules] + message["arguments"]["rules"] = rules + return await self._forward_message(message) async def configurationDone(self, message): diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index e1505a9a7..620f0bb4e 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -119,6 +119,12 @@ def __init__(self, **kwargs): from .debugger import _is_debugpy_available + self._kernel_modules = [ + m.__file__ + for m in sys.modules.values() + if hasattr(m, "__file__") and m.__file__ + ] + # Initialize the Debugger if _is_debugpy_available: self.debugger = self.debugger_class( @@ -127,6 +133,7 @@ def __init__(self, **kwargs): self._publish_debug_event, self.debug_shell_socket, self.session, + self._kernel_modules, self.debug_just_my_code, ) From 7993c995794948e9cef93ce947bb5cbbb4d43c90 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 1 Dec 2025 15:07:04 +0530 Subject: [PATCH 2/5] Introduce `filter_internal_frames` option to control internal frame filtering --- ipykernel/debugger.py | 8 +++++--- ipykernel/ipkernel.py | 1 + ipykernel/kernelbase.py | 9 ++++++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 7a158ba5b..49383810f 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -326,7 +326,7 @@ class Debugger: ] def __init__( - self, log, debugpy_stream, event_callback, shell_socket, session, kernel_modules, just_my_code=True + self, log, debugpy_stream, event_callback, shell_socket, session, kernel_modules, just_my_code=False, filter_internal_frames=True ): """Initialize the debugger.""" self.log = log @@ -337,6 +337,7 @@ def __init__( self.event_callback = event_callback self.kernel_modules = kernel_modules self.just_my_code = just_my_code + self.filter_internal_frames = filter_internal_frames self.stopped_queue: Queue[t.Any] = Queue() self.started_debug_handlers = {} @@ -577,8 +578,9 @@ async def attach(self, message): message["arguments"]["debugOptions"] = ["DebugStdLib"] # Dynamic skip rules (computed at kernel startup) - rules = [{"path": path, "include": False} for path in self.kernel_modules] - message["arguments"]["rules"] = rules + if self.filter_internal_frames: + rules = [{"path": path, "include": False} for path in self.kernel_modules] + message["arguments"]["rules"] = rules return await self._forward_message(message) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 620f0bb4e..2a89d9ea9 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -135,6 +135,7 @@ def __init__(self, **kwargs): self.session, self._kernel_modules, self.debug_just_my_code, + self.filter_internal_frames, ) # Initialize the InteractiveShell subclass diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 52d40b952..7fa1fb9dc 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -179,11 +179,18 @@ def _default_ident(self): # The ipykernel source is in the call stack, so the user # has to manipulate the step-over and step-into in a wize way. debug_just_my_code = Bool( - True, + False, help="""Set to False if you want to debug python standard and dependent libraries. """, ).tag(config=True) + # Experimental option to filter internal frames from the stack trace and stepping. + filter_internal_frames = Bool( + True, + help="""Set to False if you want to debug kernel modules. + """, + ).tag(config=True) + # track associations with current request # Private interface From fa1bd64f330a0227fd2db3d36fecda88f904c7d2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 10:16:27 +0000 Subject: [PATCH 3/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ipykernel/debugger.py | 10 +++++++++- ipykernel/ipkernel.py | 4 +--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 49383810f..a3f604f70 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -326,7 +326,15 @@ class Debugger: ] def __init__( - self, log, debugpy_stream, event_callback, shell_socket, session, kernel_modules, just_my_code=False, filter_internal_frames=True + self, + log, + debugpy_stream, + event_callback, + shell_socket, + session, + kernel_modules, + just_my_code=False, + filter_internal_frames=True, ): """Initialize the debugger.""" self.log = log diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 2a89d9ea9..aa22bbb0f 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -120,9 +120,7 @@ def __init__(self, **kwargs): from .debugger import _is_debugpy_available self._kernel_modules = [ - m.__file__ - for m in sys.modules.values() - if hasattr(m, "__file__") and m.__file__ + m.__file__ for m in sys.modules.values() if hasattr(m, "__file__") and m.__file__ ] # Initialize the Debugger From dc865b87415e28aa4fd08dcc82cb2bc059afc4aa Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 1 Dec 2025 16:14:34 +0530 Subject: [PATCH 4/5] remove ipykernel stack frame filtering from stackTrace response --- ipykernel/debugger.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index a3f604f70..15952710e 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -509,23 +509,6 @@ async def source(self, message): async def stackTrace(self, message): """Handle a stack trace message.""" reply = await self._forward_message(message) - # The stackFrames array can have the following content: - # { frames from the notebook} - # ... - # { 'id': xxx, 'name': '', ... } <= this is the first frame of the code from the notebook - # { frames from ipykernel } - # ... - # {'id': yyy, 'name': '', ... } <= this is the first frame of ipykernel code - # or only the frames from the notebook. - # We want to remove all the frames from ipykernel when they are present. - try: - sf_list = reply["body"]["stackFrames"] - module_idx = len(sf_list) - next( - i for i, v in enumerate(reversed(sf_list), 1) if v["name"] == "" and i != 1 - ) - reply["body"]["stackFrames"] = reply["body"]["stackFrames"][: module_idx + 1] - except StopIteration: - pass return reply def accept_variable(self, variable_name): From a97f48afc436ee9ca78f11824033537a95a8f780 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 1 Dec 2025 18:41:15 +0530 Subject: [PATCH 5/5] remove unnecessary assignment --- ipykernel/debugger.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 15952710e..24fe92849 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -508,8 +508,7 @@ async def source(self, message): async def stackTrace(self, message): """Handle a stack trace message.""" - reply = await self._forward_message(message) - return reply + return await self._forward_message(message) def accept_variable(self, variable_name): """Accept a variable by name."""