Skip to content

Do not recurse when getting the metadata of an NWB file in a BIDS dataset - #1958

Open
CodyCBakerPhD wants to merge 2 commits into
masterfrom
claude/fix-bids-nwb-metadata-recursion
Open

CodyCBakerPhD wants to merge 2 commits into
masterfrom
claude/fix-bids-nwb-metadata-recursion

Conversation

@CodyCBakerPhD

@CodyCBakerPhD CodyCBakerPhD commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Problem

For an NWB file in a BIDS dataset, dandi.metadata.nwb.get_metadata() took the BIDS part of the metadata from df.get_metadata(). df is an NWBBIDSAsset, and its get_metadata() also extracts the NWB metadata, via NWBAsset.get_metadata() → nwb2asset() → get_metadata(). That calls back into the same BIDS branch, so the cycle repeats until RecursionError.

The result was still correct, because NWBAsset.get_metadata() falls back to default metadata on errors at each level. But the BIDS validator ran at every one of roughly 100 levels. On ieeg_epilepsyNWB from bids-examples, one get_metadata() call made 98 nested nwb2asset() calls and took about 60 s. That is what made test_bids_nwb_metadata_integration time out on Windows CI.

Fix

Take only the BIDS metadata, via bids.BIDSAsset.get_metadata(df, ...), which was the intent of that branch. The NWB metadata is extracted by the rest of get_metadata() itself.

Measured on the same file:

nwb2asset() calls time subject_id / sex
before 98 (nested) 60.9 s 01 / U
after 0 0.7 s 01 / U

🤖 Generated with Claude Code

https://claude.ai/code/session_01Qn5WBSiQgoZoL4fytF6nEr

…aset

For an NWB file in a BIDS dataset, get_metadata() took the BIDS metadata
from NWBBIDSAsset.get_metadata(), which also extracts the NWB metadata by
calling nwb2asset() and thus get_metadata() again, and so on until
RecursionError: ~100 levels, each running the BIDS validator.  The result
was still correct, as each level falls back on errors, but it took about
a minute per file (and timed out CI on Windows).  Take only the BIDS
metadata, with BIDSAsset.get_metadata().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qn5WBSiQgoZoL4fytF6nEr
@codecov

codecov Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.82%. Comparing base (ad8f64d) to head (3ee7aba).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1958      +/-   ##
==========================================
+ Coverage   90.77%   90.82%   +0.05%     
==========================================
  Files          92       92              
  Lines       14130    14159      +29     
==========================================
+ Hits        12826    12860      +34     
+ Misses       1304     1299       -5     
Flag Coverage Δ
unittests 90.82% <100.00%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@CodyCBakerPhD CodyCBakerPhD self-assigned this Oct 12, 2026
@CodyCBakerPhD CodyCBakerPhD added cmd-validate bug Something isn't working patch Increment the patch version when merged labels Oct 12, 2026
@CodyCBakerPhD
CodyCBakerPhD marked this pull request as ready for review October 12, 2026 04:55
@CodyCBakerPhD

Copy link
Copy Markdown
Contributor Author

@yarikoptic this one was discovered back in the bids-dandisets days but fixing was delayed until closer to 'eventual' BEP032 integration

Might as well do it now though, with all the other validation improvements going on

Comment thread dandi/metadata/nwb.py Outdated
# Only the BIDS metadata: `df` is an `NWBBIDSAsset`, whose own
# `get_metadata()` would extract the NWB metadata by calling this
# function again, recursing until `RecursionError`
path_metadata = bids.BIDSAsset.get_metadata(df, digest=digest)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

but this sounds like a band-aid if there is some infinite recursion -- shouldn't it be fixed where it happens to prevent generally -- would that be possible here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure, the first version was bare-bones minimal code to do the job, but the current version is more direct to the source of the problem (not disambiguating between BIDS and non-BIDS metadata calls), which is now split

… asset

Getting the metadata of an NWB file in a BIDS dataset recursed because
of a cycle between two methods that each merge BIDS and NWB metadata:
`NWBBIDSAsset.get_metadata()` extracts the NWB metadata with
`dandi.metadata.nwb.get_metadata()`, which itself got the BIDS metadata
of the file with `get_metadata()` of its `NWBBIDSAsset`.  Calling
`BIDSAsset.get_metadata()` unbound only patched this one call site.

Instead, give BIDS assets a method returning only their BIDS metadata,
which no subclass overrides to read the content, and use it wherever
only the BIDS metadata is wanted: when extracting the metadata of an
NWB file, and in `NWBBIDSAsset.get_metadata()` and
`ZarrBIDSAsset.get_metadata()` (no change in behavior).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qn5WBSiQgoZoL4fytF6nEr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working cmd-validate patch Increment the patch version when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants