Skip to content

fix(correct,filter,clip,review): synthesize @HD when input lacks one (match fgbio) - #527

Merged
nh13 merged 1 commit into
mainfrom
nh/fix-correct-synthesize-hd-header
Jul 15, 2026
Merged

nh13 merged 1 commit into
mainfrom
nh/fix-correct-synthesize-hd-header

Conversation

@nh13

@nh13 nh13 commented Jul 10, 2026 •

Copy link
Copy Markdown
Member

Summary

fgumi commands that read an input header and pass it through (adding only @PG) propagate a missing @HD line, producing a BAM whose header starts at @PG. fgbio synthesizes @HD VN:1.6 SO:unsorted for header-less input, and most downstream tooling expects an @HD. This makes fgumi match.

New shared helper fgumi_bam_io::header::ensure_hd_record inserts @HD VN:1.6 SO:unsorted only when the header lacks one — an existing @HD (and its sort order) is left untouched — wired into the passthrough commands that can otherwise emit a header-less BAM: correct, filter, clip, review.

Scope: which commands, and why

Fixed (passthrough → written header, no guard):

  • correct, filter, clip, review

Intentionally not touched, because they already cannot emit a header-less BAM:

  • dedup, downsample, group — reject non-template-coordinate input at their sort-order guard before the writer.
  • simplex/duplex/codec — build their output header via create_unmapped_consensus_header (always carries @HD).
  • zipper — builds @HD from the sequence dictionary.

Evidence (real data)

The bug surfaced on data/raw/correct_small.bam, generated by an old fgumi 0.1.3 simulate correct-reads that emitted no @HD (header starts at @PG). Running the fixed fgumi correct on it (matching the campaign's --max-mismatches 2 --min-distance 2):

  • Output header is now @HD VN:1.6 SO:unsorted — byte-identical to the fgbio CorrectUmis baseline.
  • Record count 18,070 == fgbio 18,070.
  • Content comparison vs fgbio: IDENTICAL — 0 core-field diffs, 0 tag-value diffs. The only residue behind the previously-masking header diff is 10,134 records whose tags are in a different order (values equal), which the comparator classifies as identical.

Testing

  • Unit tests for ensure_hd_record (synthesizes when absent; preserves an existing @HD/SO).
  • End-to-end tests that run correct, filter, and clip on header-less input and assert the output carries @HD VN:1.6 SO:unsorted.
  • Full suite green: cargo nextest 2244 passed, cargo clippy --workspace --all-targets --features compare,simulate,profile-adjacency -- -D warnings -W clippy::pedantic clean, cargo fmt --check clean.

Summary by CodeRabbit

  • Bug Fixes
    • BAM processing now automatically synthesizes a standard @HD header record when missing.
    • Existing @HD records are preserved exactly (including version and all fields).
    • Header normalization is now consistent across clipping, correction, filtering, and review outputs.
    • Headerless or improperly sorted inputs are still rejected with the same validation error.
  • Tests
    • Added integration tests and helpers covering @HD synthesis and headerless-input rejection for clip and filter.

@nh13
nh13 temporarily deployed to github-actions July 10, 2026 00:47 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 39 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bc3337e5-75e4-4486-bed2-0ca04b3b4628

📥 Commits

Reviewing files that changed from the base of the PR and between 6b7ab52 and 6131db9.

📒 Files selected for processing (8)
  • crates/fgumi-bam-io/src/header.rs
  • src/lib/commands/clip.rs
  • src/lib/commands/common.rs
  • src/lib/commands/correct.rs
  • src/lib/commands/filter.rs
  • src/lib/commands/review.rs
  • tests/integration/test_clip_command.rs
  • tests/integration/test_filter_command.rs

Walkthrough

BAM header handling now guarantees an @HD record with VN:1.6 and SO:unsorted when absent, while preserving existing records. Clip, correct, filter, and review normalize headers before downstream validation or output generation, with unit and integration coverage.

Changes

Header normalization

Layer / File(s) Summary
Header normalization contract
crates/fgumi-bam-io/src/header.rs
Adds ensure_hd_record, synthesizing default @HD fields when missing and preserving existing records with unit tests for both cases.
Command pipeline integration
src/lib/commands/common.rs, src/lib/commands/clip.rs, src/lib/commands/correct.rs, src/lib/commands/filter.rs, src/lib/commands/review.rs
Exposes the shared helper and applies it across threaded, single-threaded, correction, filtering, consensus extraction, and grouped extraction paths before grouping checks or output header updates.
Headerless input validation
tests/integration/test_clip_command.rs, tests/integration/test_filter_command.rs, src/lib/commands/correct.rs
Adds @SQ-only BAM fixtures and verifies clip/filter rejection and synthesized output headers from correction.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: synthesizing missing @HD records across the listed commands to match fgbio behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nh/fix-correct-synthesize-hd-header

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.69%. Comparing base (69d3630) to head (6131db9).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #527      +/-   ##
==========================================
- Coverage   92.71%   92.69%   -0.03%     
==========================================
  Files         166      166              
  Lines      100982   101060      +78     
==========================================
+ Hits        93630    93677      +47     
- Misses       7352     7383      +31     

☔ 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.

@nh13
nh13 force-pushed the nh/fix-correct-synthesize-hd-header branch from fb62064 to b8b66e5 Compare July 10, 2026 03:48
@nh13
nh13 temporarily deployed to github-actions July 10, 2026 03:48 — with GitHub Actions Inactive
@nh13
nh13 force-pushed the nh/fix-correct-synthesize-hd-header branch from b8b66e5 to f052dc1 Compare July 11, 2026 05:53
@nh13
nh13 temporarily deployed to github-actions July 11, 2026 05:53 — with GitHub Actions Inactive
@nh13
nh13 force-pushed the nh/fix-correct-synthesize-hd-header branch from f052dc1 to 6de00f2 Compare July 13, 2026 03:49
@nh13
nh13 temporarily deployed to github-actions July 13, 2026 03:49 — with GitHub Actions Inactive
@nh13

nh13 commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/commands/clip.rs (1)

236-260: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Inconsistent guard ordering produces different rejection diagnostics for the same input depending on --threads.

In execute() (threads path), require_query_grouped runs on the raw header before ensure_hd_record, so a header-less input reports SO:<default> in the error. In execute_single_threaded(), ensure_hd_record runs first, so the same header-less input now has SO:unsorted baked in before the check, producing a different found: SO:... GO:... message for the identical failure. Both paths still reject (tests only check for the "queryname sorted or query grouped" substring), but the diagnostic detail silently diverges by execution mode. Move ensure_hd_record before require_query_grouped in execute() too (matching execute_single_threaded()) for consistent diagnostics.

🔧 Suggested fix
             let (reader, header) = create_bam_reader_for_pipeline(&self.io.input)?;
 
+            // Synthesize `@HD` VN:1.6 SO:unsorted when the input lacks one (match fgbio).
+            let header = crate::commands::common::ensure_hd_record(header)?;
+
             // CLIP3-05: fgbio's ClipBam calls Bams.requireQueryGrouped. Clipping is
             // template-based (pair clip, overlap, past-mate, mate-fix), so coordinate-
             // sorted input silently mis-groups mates. Guard the *input* header.
             crate::commands::common::require_query_grouped(
                 &header,
                 &self.io.input.display().to_string(),
             )?;
 
             // Load reference (always required for clip)
             let reference = Arc::new(ReferenceReader::new(&self.reference)?);
 
-            // Synthesize `@HD` VN:1.6 SO:unsorted when the input lacks one (match fgbio).
-            let header = crate::commands::common::ensure_hd_record(header)?;
-
             // Add `@PG` record with PP chaining to input's last program
             let header = crate::commands::common::add_pg_record(header, command_line)?;

Also applies to: 289-298

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/commands/clip.rs` around lines 236 - 260, In the threaded branch of
execute, call ensure_hd_record immediately after create_bam_reader_for_pipeline
and before require_query_grouped, then use the normalized header for the guard
and subsequent add_pg_record call. Keep the single-threaded ordering unchanged
so both execution modes produce consistent rejection diagnostics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/lib/commands/clip.rs`:
- Around line 236-260: In the threaded branch of execute, call ensure_hd_record
immediately after create_bam_reader_for_pipeline and before
require_query_grouped, then use the normalized header for the guard and
subsequent add_pg_record call. Keep the single-threaded ordering unchanged so
both execution modes produce consistent rejection diagnostics.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dd5420ae-f59a-454e-aa6d-65eb85fd69ef

📥 Commits

Reviewing files that changed from the base of the PR and between 868922c and 6de00f2.

📒 Files selected for processing (8)
  • crates/fgumi-bam-io/src/header.rs
  • src/lib/commands/clip.rs
  • src/lib/commands/common.rs
  • src/lib/commands/correct.rs
  • src/lib/commands/filter.rs
  • src/lib/commands/review.rs
  • tests/integration/test_clip_command.rs
  • tests/integration/test_filter_command.rs

@nh13
nh13 force-pushed the nh/fix-correct-synthesize-hd-header branch from 6de00f2 to 6b7ab52 Compare July 14, 2026 13:26
@nh13
nh13 had a problem deploying to github-actions July 14, 2026 13:26 — with GitHub Actions Failure
@nh13

nh13 commented Jul 14, 2026 •

Copy link
Copy Markdown
Member Author

Addressed the outside-diff finding on src/lib/commands/clip.rs (threaded-path guard ordering): moved ensure_hd_record before require_query_grouped in execute() so it matches execute_single_threaded()'s ordering, and both execution modes guard the same normalized header.

Note on the stated rationale: the diagnostics did not actually diverge today — for header-less input, header_sort_and_group_order already defaults a missing @HD to exactly SO:unsorted GO:none, byte-identical to what ensure_hd_record synthesizes, and for input that already has an @HD, ensure_hd_record is a no-op. So the reorder is a structural-consistency / future-proofing change rather than a fix for a current message divergence.

Also parameterized test_clip_rejects_headerless_input over both the single-threaded and --threads paths (matching the existing test_clip_rejects_coordinate_sorted_input rstest) so the reordered threaded path is now covered.

@nh13

nh13 commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/commands/review.rs`:
- Around line 720-722: Add an end-to-end `review` test covering input without an
`@HD` record, then inspect the generated `.consensus.bam` and `.grouped.bam`
files to assert each contains the synthesized `@HD VN:1.6 SO:unsorted` header.
Reuse the existing review test setup and output-reading helpers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5c50177b-bb7c-4f92-b101-54ab7b3f1877

📥 Commits

Reviewing files that changed from the base of the PR and between 6de00f2 and 6b7ab52.

📒 Files selected for processing (8)
  • crates/fgumi-bam-io/src/header.rs
  • src/lib/commands/clip.rs
  • src/lib/commands/common.rs
  • src/lib/commands/correct.rs
  • src/lib/commands/filter.rs
  • src/lib/commands/review.rs
  • tests/integration/test_clip_command.rs
  • tests/integration/test_filter_command.rs

Comment thread src/lib/commands/review.rs
fgumi commands that read an input header and pass it through (adding only
@pg) propagated a missing @hd line, producing a BAM whose header starts at
@pg. fgbio synthesizes @hd VN:1.6 SO:unsorted for header-less input; fgumi
now matches.

Add fgumi_bam_io::header::ensure_hd_record, which inserts @hd VN:1.6
SO:unsorted only when the header lacks one (an existing @hd and its sort
order are left untouched), and wire it into the passthrough commands that
can otherwise emit a header-less BAM: correct, filter, clip, and review.

Commands that already cannot emit a header-less BAM are intentionally not
touched: dedup, downsample, and group reject non-template-coordinate input
at their sort-order guard; simplex/duplex/codec build their output header
via create_unmapped_consensus_header; and zipper builds @hd from the
sequence dictionary.

Verified on real data: fgumi correct on the header-less
data/raw/correct_small.bam (from an old 0.1.3 simulate) now emits
@hd VN:1.6 SO:unsorted, byte-identical to the fgbio baseline, and a content
comparison vs fgbio is IDENTICAL (0 core-field and 0 tag-value diffs; the
only residue is 10,134 records with tags in a different order, values equal).
@nh13
nh13 force-pushed the nh/fix-correct-synthesize-hd-header branch from 6b7ab52 to 6131db9 Compare July 15, 2026 01:55
@nh13
nh13 temporarily deployed to github-actions July 15, 2026 01:55 — with GitHub Actions Inactive
@nh13
nh13 merged commit 8b13b48 into main Jul 15, 2026
8 checks passed
@nh13
nh13 deleted the nh/fix-correct-synthesize-hd-header branch July 15, 2026 01:58
@nh13 nh13 mentioned this pull request Jul 14, 2026

This branch was previously deployed

1 inactive deployment
github-actions — 6131db96 Deployed Jul 15, 2026 by nh13 via coverage #2570
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.

1 participant