Skip to content

Fix/duplicate behavior tree id registration - #1133

Merged
facontidavide merged 5 commits into
BehaviorTree:masterfrom
KImanalieva:fix/duplicate-behavior-tree-id-registration
Oct 24, 2026
Merged

facontidavide merged 5 commits into
BehaviorTree:masterfrom
KImanalieva:fix/duplicate-behavior-tree-id-registration

Conversation

@KImanalieva

Copy link
Copy Markdown
Contributor

Bug
When we have 2 files with the same BehaviorTree ID they get randomly chosen and executed without any warnings or errors.

Detect duplicate registrations in the shared XML parser and throw RuntimeError with both source locations (first registration vs conflicting registration). Silent map overwrite is removed; intentional reload uses BehaviorTreeFactory::clearRegisteredBehaviorTrees() before registering again.

Breaking (behavioral): Registering XML that defines an id already registered on the same factory throws where it previously overwrote silently. Callers who relied on overwrite must clearRegisteredBehaviorTrees() then register again (same pattern as PR #439).

@KImanalieva

Copy link
Copy Markdown
Contributor Author

Hi @facontidavide,

I'd love to get this merged if you think it's the right direction. The failing CI check on Windows is unrelated to this PR. Happy to fix that test as well if you'd like, or we could address it in a separate PR. Let me know how you'd like to proceed!

@peter-mitrano-ar peter-mitrano-ar left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

Comment thread include/behaviortree_cpp/bt_factory.h Outdated
Comment on lines +488 to +489
* duplicate-ID errors come from registerBehaviorTreeFrom* or
* createTreeFrom*, not from createTree().

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment struck me as odd. I would perhaps not add it. I don't think we should explain errors that can come from sub-function calls, that could get out of hand quickly.

@peter-mitrano-ar

Copy link
Copy Markdown

@facontidavide FYI the author, Kanykei, works with me at Agile Robots. This was something that bugged us so we a proposing this fix 🙂

@schornakj schornakj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we have 2 files with the same BehaviorTree ID they get randomly chosen and executed without any warnings or errors.

I don't think this is quite accurate, based on what I've observed using this library. If you have 2 XML files which both define a tree using the same ID and call registerBehaviorTreeFromFile one one then the other, the definition provided in the second file overwrites the first.

Breaking (behavioral): Registering XML that defines an id already registered on the same factory throws where it previously overwrote silently. Callers who relied on overwrite must clearRegisteredBehaviorTrees() then register again (same pattern as #439).

I wouldn't want this to be a terminating error case, since I have some situations where I do this on purpose. One use case is to test a behavior tree with many subtrees: by overwriting the registration of some subtrees with fakes that have specific behavior (always failing, always outputting a specific value to a port, etc.) I can efficiently test the control flow through the top-level tree without having to replicate a lot of interaction with my robot and sensors. I wouldn't be able to use your suggested workaround of clearing and then reregistering because at the point where I want to replace a subtree with a fake one I no longer have detailed information about which tree came from which XML file, so I wouldn't be able to construct a subset of XML files that does not contain duplicates.

In my application we addressed this by writing a registration helper function that checks for duplicate tree IDs and BT nodes IDs (and also for trees with the same IDs as nodes, since that also causes problem) and reports a failure at that scope. The replace-a-tree-with-a-fake-during-testing situation is accessed directly through the BT factory.

I'd be OK with having a warning message about this situation, since it's confusing if it sneaks up on you.

facontidavide and others added 3 commits October 24, 2026 16:00
Registering a BehaviorTree ID that already exists replaced the previous
definition silently. Making it an error breaks legitimate uses of the
deliberate last-wins behavior (a851c79): registering the same file twice,
calling createTreeFromText repeatedly on one factory, diamond includes, and
replacing a subtree with a test double.

Keep last-wins and print a warning instead, only when the previous
definition came from a different source or from the same document; reloading
a file, including it from two places, or reusing the same inline text stays
silent. Take the source from the include stack added by BehaviorTree#1250 instead of a
second current_file variable, document the behavior in bt_factory.h, revert
the clearRegisteredBehaviorTrees() calls added to existing tests, and turn
the new tests into warning/last-wins checks, plus diamond-include and
repeated-inline-text cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@facontidavide

Copy link
Copy Markdown
Collaborator

Thanks @KImanalieva, and @schornakj and @peter-mitrano-ar for the review. You're right that a silent overwrite is a footgun. But the overwrite itself is intentional (last definition wins, since a851c79), and people rely on it: registering the same file twice, calling createTreeFromText repeatedly on one factory, diamond includes, and replacing a subtree with a test double. A hard error would break all of those, and clearRegisteredBehaviorTrees() isn't a substitute because it wipes every tree.

To move this forward, I pushed a merge of master plus a commit that turns the throw into a warning and keeps last-wins. The warning is printed only when the previous definition came from a different source, or appears twice in the same document. Reloading a file, including it from two places, or reusing the same inline text stays silent. The source comes from the include stack added in #1250, so the separate current_file state is gone. Your tests are kept, rewritten to check the warning and that the last definition wins, plus new cases for diamond includes and repeated inline text. I'll squash-merge once CI is green, and you stay the commit author.

@facontidavide
facontidavide merged commit a55ea8a into BehaviorTree:master Oct 24, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants