There doesn't seem to be any obvious solution, so I've come up with a bit of a hack:
The trick is,
context.active_node is always the actual active node in the tree the user is currently editing.
context.space_data.node_tree.nodes.active (let's call it
tree_active for short) is the active node for the base level (outside all groups). If the user is editing a group,
tree_active will be a group node (because it was first selected+made active before the user hit Tab to edit it).
context.active_node is not the same as
tree_active, then we know the user is inside a group. We don't know how deep they are though, so we need to recursively check if
tree_active is the same as
context.active_node - if it's not, then we use
tree_active's node tree as the new base tree we're checking against.
If we are in a group,
tree_active will always be a group node (unless it is the same as
Here's the code I eventually came up with:
tree = context.space_data.node_tree # Base level tree
while tree.nodes.active != context.active_node:
# We are in a group, check next level
tree = tree.nodes.active.node_tree
tree will be whatever node tree the user is currently editing.