You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Valid IUPAC ambiguity symbols in BAM SEQ can make modkit pileup panic or abandon the rest of an otherwise usable record. The optimized reverse-read scanner assumes every query symbol is A/C/G/T, while both optimized and generic aligned-position paths either unwrap conversion of an ambiguous symbol or skip the entire record when only that one observation is unusable.
As a result, modkit 0.6.4 can exit 101 after writing a partial bedMethyl file, or exit 0 after silently losing a valid downstream modification call and its coverage. These behaviors also reproduce at current upstream revision 5cecc3fb3a9336068d9e3c68d5c08d678153dd2c.
Severity
Severity: High — scientific completeness and reliability
Rationale: Accepted query ambiguity can selectively remove downstream modified calls from affected reads without invalidating their MM/ML groups. Some paths fail visibly, but others count the record as failed, write no row, and exit successfully. The failure can therefore bias counts and coverage toward reads without ambiguous base observations.
User and scientific impact
Affected result or workflow: optimized and generic pileup, including reverse-aligned reads and records with ambiguity before a later valid modification call.
Direction of error: loss of valid downstream modified calls and coverage; in panic paths, a nonzero exit with a partial output file.
Likely exposure: data-dependent. It affects BAMs whose query sequences contain N or another BAM IUPAC symbol, including workflows that synthesize or transform modBAM records.
Detectability or workaround: panics are visible, but record-local loss can appear only as a failed-record log and lower counts. Pre-filtering every ambiguous read avoids the crash but discards the same scientifically usable calls.
Affected versions and environment
Released version: modkit 0.6.4
Development revision: 5cecc3fb3a9336068d9e3c68d5c08d678153dd2c
Operating system and architecture: macOS 26.6, arm64
Input format and index: coordinate-sorted BAM with BAI
Related tools: samtools 1.23.1 for the synthetic fixtures
Steps to reproduce
These fixtures are synthetic and contain no private data.
Create reference.fa:
>chr1
CCTGTACTTC
Create leading-ambiguity.sam. The N at query position 0 precedes a valid modified C at query/reference position 1; the trailing N exercises the no-pending-call path.
MM deltas count occurrences of the group's stated fundamental base in the original read orientation. An N, R, Y, or other ambiguity symbol is not a C and must not consume a C+m delta. An ambiguous aligned observation cannot be classified as a specific A/C/G/T mismatch or canonical call, but it does not invalidate later MM/ML observations in the same read.
For each of the first two fixtures, both pileup workers must therefore emit exactly this downstream 5mC row:
chr1 1 2 m 1 + 1 2 255,0,0 1 100.00 1 0 0 0 0 0 0
Observed behavior
On modkit 0.6.4:
Both leading-ambiguity commands exit 0, report approximately one failed record, and write an empty output file. The valid downstream 5mC call is silently lost.
Both softclip-ambiguity commands exit 101 after unwrapping InvalidDnaBase in pileup_processor.rs; each leaves an empty output file.
The paired optimized command writes only the forward row, then exits 101 from base_mods_adapter.rs after the reverse record reaches an unreachable!() branch. The 51-byte partial output has SHA-256 794047e609d4b3993f27bff54b1a6249c93d1d472668b0f766a91fd761eb4f6d.
With the bounded repair, optimized and generic execution both emit the exact expected row for each downstream-call fixture. All four files are 50 bytes with SHA-256 41458d8efc760a6849bbc6e95f2322be5d02c4a602ec4e24d24325d942866319. The paired optimized command exits 0 and emits both rows; its 102-byte output has SHA-256 5a315a8f4557d043736415c4f9743619f39f8f7d31974b3ed620708859df1784.
Expected behavior
Valid BAM IUPAC query symbols do not panic either pileup worker.
Ambiguity symbols do not consume A/C/G/T MM-group deltas.
Only the ambiguous aligned observation is skipped; pending state and every valid downstream modification remain available to the rest of the record.
Ambiguous observations are not misclassified as a canonical call, mismatch, deletion, or No Call for an invented A/C/G/T identity.
Valid ambiguity does not increment the failed-record count.
Optimized and generic workers agree on scientific positions, modification identities, counts, and valid coverage.
Root-cause evidence
Reverse-read A/C/G/T frequency accounting treats every other BAM query symbol as unreachable at base_mods_adapter.rs:68-87.
The optimized worker either abandons the whole record on a failed query-base conversion or unwraps the same conversion while advancing pending modification state at pileup_processor.rs:320-420.
The generic worker has an analogous pending-state unwrap path at pileup_processor.rs:830-900, plus record-level skips at neighboring conversion sites.
Proposed fix scope
Ignore non-ACGT query symbols when calculating optimized reverse-read A/C/G/T frequencies and MM-delta offsets.
Complement the complete BAM IUPAC alphabet explicitly so reverse scanning remains orientation-correct.
Centralize fallible A/C/G/T conversion for aligned query observations in the two pileup workers.
Skip only the ambiguous observation, saving any already-advanced pending modification state before continuing to the next aligned position.
Add exact forward/reverse adapter and optimized/generic end-to-end regressions.
Non-goals
No support claim for MM groups whose fundamental base is N or U; this change concerns ordinary A/C/G/T MM groups on reads containing ambiguous query symbols.
No new support for negative-strand MM groups.
No malformed MM/ML recovery, generic record-error policy, or output rollback/atomicity change.
No classification of an ambiguous observation as canonical, mismatch, deletion, or No Call.
No requirement that optimized and generic bedMethyl column-4 motif labels be byte-identical where their existing documented labels differ.
No threading, scheduling, threshold, schema, or performance redesign.
Acceptance criteria
The two downstream-call fixtures emit exactly one 5mC row with modified count and valid coverage equal to one in both workers.
The paired forward/reverse fixture emits both rows and exits 0 in optimized mode.
A direct adapter regression proves exact forward and reverse positions and ML-derived qualities when ambiguity symbols occur between canonical bases.
All supported BAM IUPAC complement pairs are covered without consuming unrelated MM deltas.
PR #597, Allow N, by @killercup, correctly identifies and addresses the initial reverse-frequency crash and several query-base conversions. As of its current head a6115966351539b1f30d917eaf450f1bd4658e43, it remains open and materially overlaps this finding. The exact downstream-call fixtures above additionally exercise optimized and generic q < next modification handling and generic pending-state advancement, which still abandon the record or panic in that patch. This report is intended to make the remaining behavior and exact scientific oracle explicit, not to discount that contribution.
The maintainer noted on Allow N #597 that this adapter is a hot pileup loop and requested performance evidence. The bounded repair has an existing representative direct-RNA byte/performance guard and should retain that evidence in any completion PR.
Summary
Valid IUPAC ambiguity symbols in BAM
SEQcan makemodkit pileuppanic or abandon the rest of an otherwise usable record. The optimized reverse-read scanner assumes every query symbol is A/C/G/T, while both optimized and generic aligned-position paths either unwrap conversion of an ambiguous symbol or skip the entire record when only that one observation is unusable.As a result, modkit 0.6.4 can exit 101 after writing a partial bedMethyl file, or exit 0 after silently losing a valid downstream modification call and its coverage. These behaviors also reproduce at current upstream revision
5cecc3fb3a9336068d9e3c68d5c08d678153dd2c.Severity
Severity: High — scientific completeness and reliability
Rationale: Accepted query ambiguity can selectively remove downstream modified calls from affected reads without invalidating their MM/ML groups. Some paths fail visibly, but others count the record as failed, write no row, and exit successfully. The failure can therefore bias counts and coverage toward reads without ambiguous base observations.
User and scientific impact
pileup, including reverse-aligned reads and records with ambiguity before a later valid modification call.Nor another BAM IUPAC symbol, including workflows that synthesize or transform modBAM records.Affected versions and environment
5cecc3fb3a9336068d9e3c68d5c08d678153dd2cSteps to reproduce
These fixtures are synthetic and contain no private data.
Create
reference.fa:Create
leading-ambiguity.sam. TheNat query position 0 precedes a valid modified C at query/reference position 1; the trailingNexercises the no-pending-call path.Create
softclip-ambiguity.sam. The first modified C is soft-clipped, then an alignedNoccurs before the second valid modified C.Build and index both BAMs:
Run both the optimized (
--modified-bases C:m) and generic paths:The optimized reverse-scanner panic can be reproduced independently with this paired fixture.
Create
paired-reference.fa:Create
paired-reverse.sam:Control or independent oracle
MM deltas count occurrences of the group's stated fundamental base in the original read orientation. An
N,R,Y, or other ambiguity symbol is not a C and must not consume aC+mdelta. An ambiguous aligned observation cannot be classified as a specific A/C/G/T mismatch or canonical call, but it does not invalidate later MM/ML observations in the same read.For each of the first two fixtures, both pileup workers must therefore emit exactly this downstream 5mC row:
Observed behavior
On modkit 0.6.4:
InvalidDnaBaseinpileup_processor.rs; each leaves an empty output file.base_mods_adapter.rsafter the reverse record reaches anunreachable!()branch. The 51-byte partial output has SHA-256794047e609d4b3993f27bff54b1a6249c93d1d472668b0f766a91fd761eb4f6d.With the bounded repair, optimized and generic execution both emit the exact expected row for each downstream-call fixture. All four files are 50 bytes with SHA-256
41458d8efc760a6849bbc6e95f2322be5d02c4a602ec4e24d24325d942866319. The paired optimized command exits 0 and emits both rows; its 102-byte output has SHA-2565a315a8f4557d043736415c4f9743619f39f8f7d31974b3ed620708859df1784.Expected behavior
Root-cause evidence
base_mods_adapter.rs:68-87.base_mods_adapter.rs:466-474.pileup_processor.rs:320-420.pileup_processor.rs:830-900, plus record-level skips at neighboring conversion sites.Proposed fix scope
Non-goals
NorU; this change concerns ordinary A/C/G/T MM groups on reads containing ambiguous query symbols.Acceptance criteria
Reproduction artifacts
reference.faf3e4974762aaceac59f73162525a2c93a27baf1051ef31663fe2530dad06cd72leading-ambiguity.sam6bf5f06412eeab65fd7a92c2990ef09587f30a7e4963075a77573e4f4101eee2leading-ambiguity.bam80dbb07cf3f5e412a84550ca9f33333aff0de25819ea5ba02c57b1b03f074187softclip-ambiguity.sam5156b5d8c74e5a8c5ac894e2cc7e851e5cbe5a7a35843343e8c3824eeb64b04csoftclip-ambiguity.bam4d17850fdb57cbb03c2411728634881d4fc2c2dbde007571f737266179c7136cpaired-reference.fa9bc64067eb65501ab4db210fc61868bc2785dd27de2ec0fd000b6bd07dda553fpaired-reverse.sam53be83b534c050a525197b0b148d047fbe97ddf825927a30f61abd1d50add0c7paired-reverse.bam4979d3dbbfd695fcee45d7f53383e67c2e636d509e2b52ac3dd0da1175f22e14Related work
a6115966351539b1f30d917eaf450f1bd4658e43, it remains open and materially overlaps this finding. The exact downstream-call fixtures above additionally exercise optimized and genericq < next modificationhandling and generic pending-state advancement, which still abandon the record or panic in that patch. This report is intended to make the remaining behavior and exact scientific oracle explicit, not to discount that contribution.