Conversation
Tree._branch_lookup maps a branch name to its index in Tree._branch_data. When
a jagged branch's generated counter name matched a branch that had already been
declared, the colliding datum was removed with 'del self._branch_data[...]',
which shifts every later datum down by one while _branch_lookup keeps pointing
at the old indices.
mktree with scalar 'nx', scalar 'y' and jagged 'x' produced the lookup
{'nx': 1, 'y': 1, 'x': 2}: 'y' aliased the generated counter, and extending the
tree failed. Replace the datum in place instead, which also keeps the counter
ahead of the jagged branch it counts.
Assisted-by: claude-code:claude-opus-5[1m]
Codecov Report❌ Patch coverage is
❌ Your patch check has failed because the patch coverage (73.91%) is below the target coverage (98.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files
|
Jagged branches that share a counter name now share the counter datum, instead of the later one orphaning the earlier one's counter. extend no longer fails comparing shared counters because of their big-endian dtype. A generated counter only replaces a scalar integer branch; colliding with any other branch raises a ValueError. Tests are condensed and renamed after the PR number. Assisted-by: Claude Code:claude-opus-5-5
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Record/counter collision cases can still be silently ignored or create duplicate/orphaned branches.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
What changed in this PR
Fixes TTree branch lookup corruption caused by generated counter-name collisions during mktree and extend.
Changes:
- Replaces scalar counter collisions in place.
- Reuses compatible generated counters.
- Rejects incompatible collisions and adds regression tests.
| File | Summary |
|---|---|
src/uproot/writing/_cascadetree.py |
Updates counter collision and comparison handling. |
tests/test_1698_cascadetree_counter_collisions.py |
Adds collision regression tests. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
A record or record field whose name matches a counter generated by an earlier jagged branch was silently dropped, or for dict records appended as a second branch with the same name. Such collisions now raise a ValueError, and dict record fields no longer duplicate existing names. Assisted-by: Claude Code:claude-opus-5-5

🤖 AI text below 🤖
Addresses finding 1 of "PR 5" in #1688.
Tree._branch_lookupmaps a branch name to its index inTree._branch_data. When a jagged branch's generated counter name collides with a branch that was already declared, the colliding datum was removed withdel:https://github.com/scikit-hep/uproot5/blob/main/src/uproot/writing/_cascadetree.py#L203-L207
which shifts every later datum down by one, while
_branch_lookupkeeps pointing at the old indices.yaliases the generated counter, and extending the tree fails.Changes, depending on what the counter collides with:
counter_name=lambda n: "nJet"forJet_ptandJet_eta): the existing counter datum is reused. Previously the earlier branch kept a reference to a counter that was never written, and writing the tree failed with astruct.error.extendalso failed when comparing the shared counter, becauseawkward.to_numpyrejects its big-endian dtype; it now usesnumpy.asarray.mktreenow raises aValueError. Previously the declared branch was silently replaced, leaving orphaned branches (the jagged branch's own counter, or the record's fields) that madeextendfail with unrelated errors.mktreealso raises aValueError. Previously the record or field was silently dropped, or, for a dict record, appended as a second branch with the counter's name.Note this needs
mktreeto reproduce: since v5.7.0 dict assignment produces an RNTuple, which does not go through this code.