Skip to content

Fix #108: refuse to fold when chain IDs are exhausted - #112

Merged
wilke merged 1 commit into
mainfrom
fix/108-chain-id-exhaustion
Aug 19, 2026
Merged

Fix #108: refuse to fold when chain IDs are exhausted#112
wilke merged 1 commit into
mainfrom
fix/108-chain-id-exhaustion

Conversation

@wilke

@wilke wilke commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Closes #108.

EntityList.add assigned chain IDs with _CHAIN_IDS[len(self.entities) % 26], so entity 27 silently got chain ID A again — duplicate chains that Boltz YAML and Chai FASTA headers both accept and then fold as the wrong complex. Reachable today because the 26-record limit is per FASTA file, so --protein a.fasta --dna b.fasta with 20 records each passes validation.

add now raises before appending, and the modulo is gone so the index cannot wrap even if the guard moved. Enforced in add as a data-model invariant, the same rationale as the CCD validation already there — it covers the CLI, the batch job-file path, adapters, and library use.

--force deliberately does not lift it. --force relaxes the per-file sequence and total-residue limits, which are soft guards; there is no 27th single-letter chain ID to hand out, so this one is structural. The message says so, and a test pins it.

cli.py gained a small wrapper so every entity flag reports these invariants as Error: <message> with exit 2 rather than a traceback — previously only ligand errors were translated. 598 passed, 10 skipped.

EntityList.add assigned chain IDs with
``_CHAIN_IDS[len(self.entities) % len(_CHAIN_IDS)]``. Past 26 entities
the modulo wrapped: entity 27 silently got chain ID 'A' again. Duplicate
chain IDs are structurally invalid but are accepted verbatim by the
downstream input formats (Boltz YAML `id:`, Chai FASTA headers), so the
job folded the wrong complex instead of failing.

Reachable today because the 26-record limit is enforced per FASTA file in
parse_fasta_entities, not across inputs: `--protein a.fasta --dna b.fasta`
with 20 records each builds 40 entities and passes every check.

The cap now lives in ``add`` as a data-model invariant, next to the CCD
validation and for the same reason — it covers the CLI, the batch job-file
path, adapters, and direct library use. The modulo is gone, so the index
can no longer wrap silently. --force deliberately does not lift it: it
lifts the per-file sequence and residue limits, which are soft guards,
whereas there simply is no 27th single-letter chain ID. The message says
so, names the shared budget across all inputs, and suggests splitting the
complex into separate jobs.

Both CLI entity-collection paths now funnel every add through a small
wrapper that reports these invariants as click.UsageError, so the user
gets `Error: <message>` (exit 2) at submit time rather than a traceback —
previously only the ligand CCD error was translated.

Tests: boundary (26 build with distinct chain IDs A-Z, 27th raises),
ligands/SMILES share the budget, the rejected entity is not appended, the
CCD error is not shadowed below the cap, message content, multi-file
combination via _build_entity_list, and --force not bypassing it.

Suite: 598 passed, 10 skipped (was 589 passed, 10 skipped).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DEWo4xvL59PHC1QUvtTV9F
@wilke
wilke merged commit 51a39d6 into main Aug 19, 2026
1 check passed
@wilke
wilke deleted the fix/108-chain-id-exhaustion branch August 19, 2026 01:37
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.

entities: chain IDs silently wrap past 26 combined entities, producing duplicate chains

1 participant