Files

645 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "Bug Patterns Reference"
task: ""
lineage_type: import
upstream_source: https://github.com/mims-harvard/ToolUniverse/blob/e2520a96/skills/devtu-self-evolve/references/bug-patterns.md
upstream_sha: e2520a96
imported_at: 2026-06-26
prompt_class: prompt
upstream_changes: accepted
author: upstream
validated: false
---
# Bug Patterns Reference
Detailed code-level fix patterns discovered through role-play debug rounds.
## Table of Contents
**Rounds 52–57 (core patterns)**
- [Regex Overmatch: Mutation vs Fusion](#regex-overmatch)
- [Silent Parameter Alias](#silent-parameter-alias)
- [Always-Fires Conditional](#always-fires-conditional)
- [Silent Normalization](#silent-normalization)
- [Notation / Case Sensitivity](#notation-case)
- [Truncation Not Top-Level](#truncation)
- [Gene Symbol Substring Match](#gene-symbol)
- [try/except Indentation](#try-except)
- [Multi-Word Search Hints](#multi-word-search)
- [Expression Units Inference](#expression-units)
- [Auto-Selected vs Explicit Study](#auto-selected-study)
- [CIViC Therapy + AND Logic](#civic-therapy-and)
**Rounds 68–78 (API-specific patterns)**
- [DGIdb Client-Side Filtering](#dgidb-client-side)
- [CPIC PostgREST Filter Prefix](#cpic-postgrest)
- [CPIC Recommendation Join](#cpic-join)
- [KEGG Organism-Specific Gene Search](#kegg-organism)
- [HPA Deprecated Column](#hpa-deprecated)
- [PDBePISA Operation from Config](#pdbepisa-operation)
- [GTEx Dataset Selection](#gtex-dataset)
- [GxA Silent geneId Ignore](#gxa-geneid)
- [ExpressionAtlas Schema Key Mismatch](#expressionatlas-schema)
- [Proteins API Human Priority](#proteins-api-human)
- [RCSB Null Type in Schema](#rcsb-null)
- [ClinVar Search Field Tags](#clinvar-fields)
- [ENCODE Tissue+Organism Filter](#encode-filter)
- [GEO Double-Adding Search Terms](#geo-double)
- [CPIC Warfarin Algorithm Guideline](#cpic-warfarin)
- [PharmGKB clinpgxid vs pharmgkbid](#pharmgkb-id)
- [ENCODE ChIP-seq Assay Title](#encode-assay)
- [RegulomeDB Parameter Names](#regulomedb)
- [BindingDB API Typo](#bindingdb)
- [ProteomeXchange Title and Instruments](#proteomexchange)
- [MetabolomicsWorkbench moverz URL](#metworkbench)
- [MetaboLights Pagination Ignored](#metalights)
- [ChEBI Search Nulls and HTML](#chebi)
- [HMDB No Open API](#hmdb)
- [PharmGKB relatedGenes Filter](#pharmgkb-related)
---
## Regex Overmatch: Mutation vs Fusion {#regex-overmatch}
**Tools**: civic_search_evidence_items, civic_search_variants
**Bug IDs**: BUG-56A-001
A regex that normalizes `GENE1-GENE2` fusion notation can accidentally match `GENE-MutationCode` (e.g., `EGFR-T790M`, `BRAF-V600E`, `KRAS-G12C`) because mutation codes start with an uppercase letter.
**Fix**: Use a lambda that checks if the second part matches HGVS protein-change format before applying the substitution:
```python
def _maybe_fuse(m):
second = m.group(2)
# Protein-change: single letter + digits + letter/asterisk (e.g. T790M, V600E, G12C)
if re.match(r"^[A-Z]\d+[A-Z*]?$", second):
return m.group(0) # leave unchanged
return m.group(1) + "::" + second
normalized = re.sub(r"\b([A-Z][A-Z0-9]*)-([A-Z][A-Z0-9]+)\b", _maybe_fuse, mol_profile)
```
When the input matches the mutation pattern and returns 0 results, add a hint:
```python
elif re.search(r"\b[A-Z][A-Z0-9]*-[A-Z]\d+[A-Z*]?\b", mol_profile):
space_form = mol_profile.replace("-", " ", 1)
mp_warn += f" If '{mol_profile}' is a point mutation, try molecular_profile='{space_form}'..."
```
---
## Silent Parameter Alias
**Tools**: SYNERGxDB_search_combos
**Bug IDs**: BUG-55A-011
Users pass `cancer_type` but tool only accepts `sample`, `tissue_name`, `tissue`. Parameter silently dropped → all-tissue results returned with `status: "success"`.
**Fix**:
```python
sample = (
arguments.get("sample")
or arguments.get("tissue_name")
or arguments.get("tissue")
or arguments.get("cancer_type") # ADD aliases here
)
```
---
## Always-Fires Conditional
**Tools**: GtoPdb_get_interactions
**Bug IDs**: BUG-55A-002, BUG-55A-003, BUG-55B-001
`item.get("approved")` on interaction records returns `None` always (field only exists on `/ligands` objects). So `has_approved` was always `False` → conditional note always fired. Also used wrong category language ("kinase/enzyme") for GPCR targets.
**Fix**: Remove the conditional check; always emit the note as factual guidance. Use neutral language; embed the queried gene symbol:
```python
if isinstance(data, list) and len(data) > 0:
_chembl_target = gene_symbol or result.get("queried_target", {}).get("name", "the target")
result["coverage_note"] = (
"GtoPdb interactions list pharmacological research compounds — approved "
"drugs for this target may not be represented. For approved drugs and "
"clinical compounds, use ChEMBL_get_drug_mechanisms or "
f"ChEMBL_search_molecules with target_name='{_chembl_target}'."
)
```
---
## Silent Normalization
**Tools**: civic_search_evidence_items, civic_search_variants
**Bug IDs**: BUG-55A-008, BUG-55B-005
CIViC normalizes therapy (lowercase → Title Case) and molecular_profile (BCR-ABL1 → BCR::ABL1) without disclosing the transformation. Users debugging empty results can't see what was tried.
**Fix**: Track pre-normalization values, add a `normalization_note` field to the result:
```python
_therapy_normalized_from = None
_mp_normalized_from = None
# When normalizing therapy:
_therapy_normalized_from = therapy
arguments["therapy"] = therapy.title()
# After result is built:
_norm_parts = []
if _therapy_normalized_from:
_norm_parts.append(f"therapy '{_therapy_normalized_from}' → '{arguments.get('therapy')}' (CIViC uses Title Case)")
if _mp_normalized_from:
_norm_parts.append(f"molecular_profile '{_mp_normalized_from}' → '{arguments.get('molecular_profile')}' ...")
if _norm_parts:
result["normalization_note"] = "Input auto-normalized: " + "; ".join(_norm_parts) + "."
```
---
## Notation / Case Sensitivity {#notation-case}
**Tools**: civic_search_evidence_items
**Bug IDs**: BUG-55B-005 (fusion), BUG-53B-002 (therapy case)
CIViC uses `::` for gene fusions (`BCR::ABL1 Fusion`, `EML4::ALK Fusion`). Users write hyphenated forms (`BCR-ABL1`) → 0 results.
CIViC therapy names are Title Case (`"Sotorasib"` not `"sotorasib"`).
**Fix (fusion normalization)**:
```python
import re as _re
mol_profile = arguments.get("molecular_profile")
if mol_profile and isinstance(mol_profile, str):
normalized_mp = _re.sub(
r"\b([A-Z][A-Z0-9]*)-([A-Z][A-Z0-9]+)\b",
r"\1::\2",
mol_profile,
)
if normalized_mp != mol_profile:
_mp_normalized_from = mol_profile
arguments = dict(arguments)
arguments["molecular_profile"] = normalized_mp
```
**Fix (therapy case)**:
```python
if therapy == therapy.lower() or therapy == therapy.upper():
_therapy_normalized_from = therapy
arguments["therapy"] = therapy.title()
```
---
## Truncation Not Top-Level {#truncation}
**Tools**: CancerPrognosis_get_gene_expression
**Bug IDs**: BUG-55A-006
Truncation info (e.g., 500 of 1100 samples returned) buried inside `data.note` string. Researchers reading top-level fields miss it.
**Fix**: Add top-level flags:
```python
response = {"status": "success", "data": result_data}
if len(values) > max_samples:
response["truncated"] = True
response["truncation_note"] = (
f"Returning {len(values[:max_samples])} of {len(values)} samples. "
f"Pass max_samples={len(values)} (up to 2000) to retrieve the full dataset."
)
return response
```
---
## Gene Symbol Substring Match {#gene-symbol}
**Tools**: GtoPdb_get_interactions
**Bug IDs**: BUG-54B-001
`?name=AR` returns all targets containing "AR" (13+ targets). `?geneSymbol=AR` is the unambiguous HGNC lookup.
**Fix**: Try `?geneSymbol=` first; fall back to `?name=` if nothing returned:
```python
gs_url = f"{base_url}/targets?{urlencode({'geneSymbol': gene_symbol})}"
gs_resp = request_with_retry(...)
if gs_resp.status_code == 200:
gs_targets = gs_resp.json()
if gs_targets:
target_id = gs_targets[0]["targetId"]
# Fall back to ?name= only if geneSymbol returned nothing
if target_id is None:
lookup_url = f"{base_url}/targets?{urlencode({'name': gene_symbol})}"
...
```
---
## try/except Indentation {#try-except}
Every `try:` MUST have an `except` at the **exact same indentation level**. A common error is placing code after `try:` at the same level as the `try:` keyword — Python sees the try block as having no except.
```python
# CORRECT
try: # 16 spaces
resp = ... # 20 spaces (inside try)
if resp.ok: # 20 spaces (still inside try)
...
except Exception: # 16 spaces — matches try
pass
# WRONG — SyntaxError
try: # 16 spaces
resp = ... # 20 spaces
if resp.ok: # 16 spaces — OUTSIDE try, before except!
...
except Exception: # 16 spaces — Python: "try without except"
pass
```
---
## Multi-Word Search Hints {#multi-word-search}
**Tools**: GtoPdb_search_targets, GtoPdb_search_ligands
**Bug IDs**: BUG-54B-002
`name="androgen receptor"` returns empty — GtoPdb text search doesn't reliably match multi-word phrases.
**Fix**: When count=0 and query has a space, add a hint suggesting first word only:
```python
if result["count"] == 0 and name_q and " " in str(name_q):
first_word = str(name_q).split()[0]
result["multi_word_hint"] = (
f"GtoPdb text search may not match multi-word phrases like '{name_q}'. "
f"Try a single keyword: name='{first_word}'."
)
```
---
## Expression Units Inference {#expression-units}
**Tools**: CancerPrognosis_get_gene_expression
**Bug IDs**: BUG-54A-002
`_get_expression_units(profile_id)` infers units from the profile ID string. Some studies use misleading suffixes (e.g., `aml_ohsu_2022` uses `_rna_seq_v2_mrna` but stores log2 RPKM).
**Fix**: Return `(profile_id, profile_name)` from `_get_mrna_profile()` and prefer the actual API description over inference:
```python
def _get_expression_units(self, profile_id, profile_name=None):
if profile_name:
return profile_name # Prefer actual API name
# ... fallback inference from profile_id
```
---
## Auto-Selected vs Explicit Study {#auto-selected-study}
**Tools**: CancerPrognosis_get_gene_expression
**Bug IDs**: BUG-54A-004
`study_note` always said "Auto-selected study_id=..." even when user explicitly passed a full study ID like `brca_tcga`.
**Fix**: Detect explicit specification and use a different message:
```python
study_was_explicit = (
cancer == study_id_arg
and study_id_arg
and "_" in str(study_id_arg)
and str(study_id_arg).upper() not in TCGA_STUDY_MAP
and str(study_id_arg).upper() not in _CANCER_NAME_ALIASES
)
"study_note": (
f"Using explicitly specified study_id='{study_id}'."
if study_was_explicit
else f"Auto-selected study_id='{study_id}'. Use CancerPrognosis_search_studies "
"to find alternative cohorts for this cancer type."
),
```
---
## CIViC Therapy + AND Logic {#civic-therapy-and}
**Tools**: civic_search_evidence_items, civic_search_variants
**Bug IDs**: BUG-54A-001, BUG-54A-005
1. When both `query` and `variant_name` are provided, one silently wins. Fix: apply AND logic.
2. When `molecular_profile + therapy` returns 0, silently fails. Fix: auto-probe without therapy to surface available therapy names.
**Fix (AND logic)**:
```python
raw_query = arguments.get("query")
raw_variant_name = arguments.get("variant_name") or arguments.get("variant")
_both = raw_query and raw_variant_name and raw_query != raw_variant_name
if _both:
query_term = raw_query
_secondary_term = raw_variant_name
else:
query_term = raw_query or raw_variant_name
_secondary_term = None
# Later, apply AND filter:
if _secondary_term:
filtered = [v for v in filtered if _secondary_term.lower() in v.get("name","").lower()]
result["filter_note"] = f"Applied AND logic for query+variant_name."
```
**Fix (therapy auto-probe)**:
```python
if mol_profile and therapy and not disease and len(evidence_nodes) == 0:
probe_args = {k: v for k, v in arguments.items() if k != "therapy"}
probe_nodes = civic_query(probe_args)
available_therapies = sorted({
t.get("name") for node in probe_nodes
for t in node.get("therapies", []) if t.get("name")
})
result["therapy_warning"] = (
f"No evidence for '{mol_profile}' + therapy='{therapy}'. "
f"Available therapies: {available_therapies[:10]}"
)
```
---
## DGIdb Client-Side Filtering (Feature-68A-001/002)
**Tools**: DGIdb_get_drug_gene_interactions
**Pattern**: API ignores `interaction_types` and `sources` filters server-side → returns everything.
**Fix**: Fetch all, filter client-side:
```python
if interaction_types:
results = [r for r in results if r.get("interactionTypes") and
any(it in interaction_types for it in r["interactionTypes"])]
if sources:
results = [r for r in results if r.get("source") in sources]
# Always wrap with status
return {"status": "success", "count": len(results), "data": results}
```
---
## CPIC PostgREST Filter Prefix (Feature-68A-004)
**Tools**: CPICSearchPairsTool
**Pattern**: PostgREST requires equality filter as `eq.VALUE`, not bare `VALUE`.
**Fix**: Auto-prepend `eq.` if not already present:
```python
def _postgrest_eq(value):
v = str(value)
return v if v.startswith("eq.") else f"eq.{v}"
params["genesymbol"] = _postgrest_eq(gene_symbol)
```
---
## CPIC Recommendation Join (Feature-68A-006)
**Tools**: CPIC_get_recommendations
**Pattern**: `/recommendation` endpoint doesn't include drug name — need to join `/drug` table.
**Fix**: Use `?select=*,drug(name)` in the URL query string for PostgREST joins.
---
## KEGG Organism-Specific Gene Search (Feature-68B-001)
**Tools**: kegg_find_genes
**Pattern**: `/find/genes/{keyword}` ignores organism param → returns all organisms.
**Fix**: Use `/find/{organism}/{keyword}`:
```python
url = f"https://rest.kegg.jp/find/{organism}/{keyword}" if organism else f"https://rest.kegg.jp/find/genes/{keyword}"
```
---
## HPA Deprecated Column (Feature-68B-002)
**Tools**: HPA_get_protein_expression
**Pattern**: `ppi` column was deprecated; API returns error listing alternatives (`enhanced`, `supported`, `approved`, `uncertain`).
**Fix**: Remove `ppi` from select list; return error message with alternatives when API responds with column-not-found error.
---
## PDBePISA Operation from Config (Feature-68B-006)
**Tools**: PDBePISA tools
**Pattern**: `operation` param exposed as public parameter → users pass wrong values.
**Fix**: Read `operation` from the tool's JSON `fields` config, never expose as public argument.
---
## GTEx Dataset Selection (Feature-69A-001/002)
**Tools**: GTEx_get_median_gene_expression
**Pattern**: `gtex_v10` returns empty for `medianGeneExpression` endpoint; only `gtex_v8` works reliably.
**Fix**: Always default to `gtex_v8`. Add note in result:
```python
dataset = arguments.get("dataset", "gtex_v8")
if dataset == "gtex_v10":
result["dataset_note"] = "gtex_v10 may return empty for this endpoint; gtex_v8 is recommended."
```
---
## GxA Silent geneId Ignore (Feature-69A-003)
**Tools**: ExpressionAtlas_get_experiment
**Pattern**: `/experiments/{accession}` API ignores `geneId` parameter silently.
**Fix**: Apply `geneId` filter client-side on the returned data.
---
## ExpressionAtlas Schema Key Mismatch (Feature-69A-004)
**Tools**: ExpressionAtlas tools
**Pattern**: `return_schema` key `experiments` but code returns `baseline_experiments`.
**Fix**: Make key in `return_schema` match exactly what the code produces. Use `baseline_experiments`.
---
## Proteins API Human Priority (Feature-69A-007)
**Tools**: proteins_api_search
**Pattern**: Gene/name searches return non-human results first.
**Fix**: Add `taxid=9606` default for gene and name searches to prioritize human entries.
---
## RCSB Null Type in Schema (Feature-69B-002/010)
**Tools**: RCSB tools
**Pattern**: `return_schema` has `type: null` which is invalid JSON Schema.
**Fix**: Use `["array", "null"]` or `["string", "null"]` for nullable types. Also avoid using protein-only PDB IDs as test examples — use IDs that represent the tested operation.
---
## ClinVar Search Field Tags (Feature-70B-004/005)
**Tools**: ClinVar tools
**Pattern**: `[variant_id]` is not a valid eSearch field → use `[uid]`. `[disease/phenotype]` not valid → use bare condition text (no brackets).
**Fix**:
```python
# Wrong:
query = f"{variant_id}[variant_id]"
query = f"{condition}[disease/phenotype]"
# Right:
query = f"{variant_id}[uid]"
query = condition # bare text, NCBI searches all fields
```
---
## ENCODE Tissue+Organism Filter (Feature-70A-003)
**Tools**: ENCODE_search_experiments
**Pattern**: Combining tissue + organism filter → HTTP 404.
**Fix**: Fall back without organism filter when 404 occurs:
```python
try:
resp = requests.get(url_with_organism)
if resp.status_code == 404:
resp = requests.get(url_without_organism)
except Exception:
...
```
---
## GEO Double-Adding Search Terms (Feature-70A-008)
**Tools**: geo_search_datasets
**Pattern**: Code adds "methylation" to query even when it's already present.
**Fix**: Check before appending:
```python
if "methylation" not in query.lower():
query += " methylation"
```
---
## CPIC Warfarin Algorithm Guideline (Feature-70B-001)
**Tools**: CPIC_get_recommendations
**Pattern**: Warfarin guideline (ID 100425) uses `algorithm` endpoint, not `/recommendation`. Returns 0 rows from recommendation endpoint.
**Fix**: Detect warfarin (or any guideline that uses algorithm-based dosing) and redirect to `/algorithm` endpoint or explain in the response.
---
## PharmGKB clinpgxid vs pharmgkbid (Feature-70B-002/011)
**Tools**: PharmGKB tools
**Pattern**: CPIC `list_guidelines` returns `clinpgxid`, but code uses `pharmgkbid` → 404 from PharmGKB.
**Fix**: Use `clinpgxid` from CPIC response when building PharmGKB URLs.
---
## ENCODE ChIP-seq Assay Title (Feature-73B)
**Tools**: ENCODE_search_experiments
**Pattern**: Querying `assay_title=ChIP-seq` returns 0; correct value is `TF ChIP-seq`.
Also: organism filter uses wrong nested field name.
**Fix**: Map user-friendly aliases to ENCODE-specific values:
```python
ASSAY_ALIASES = {"ChIP-seq": "TF ChIP-seq", "CHIP": "TF ChIP-seq"}
assay_title = ASSAY_ALIASES.get(assay_title, assay_title)
```
---
## RegulomeDB Parameter Names (Feature-73B)
**Tools**: RegulomeDB tools
**Pattern**: `assembly=hg19` is wrong → use `genome=GRCh38`. Test example `rs123456` doesn't exist → use `rs4994`.
**Fix**: Update parameter names and test examples to match actual API.
---
## BindingDB API Typo (Feature-73A)
**Tools**: BindingDB tools
**Pattern**: API endpoint has typo: `getLindsByXxx` (not `getLigandsByXxx`). Also response key is `bdb.affinities` not `affinities`.
**Fix**: Use exact typo as in the real API URL. Access `response["bdb.affinities"]`.
---
## ProteomeXchange Title and Instruments (Feature-73A)
**Tools**: ProteomeXchange tools
**Pattern**: `title` field is plain string not dict. `instruments` use `{name, accession}` not nested `terms`.
**Fix**: Access `dataset.get("title")` directly. For instruments: `inst.get("name")` not `inst["terms"][0]["name"]`.
---
## MetabolomicsWorkbench moverz URL (Feature-73A/74A)
**Tools**: MetabolomicsWorkbench tools
**Pattern**: `moverz` endpoint needs database prefix: `/moverz/{database}/{mz}/{adduct}/{tolerance}`. Also `exactmass` endpoint is broken → use `moverz/REFMET/{mass}/M/{tolerance}`.
**Fix**:
```python
url = f"https://www.metabolomicsworkbench.org/rest/moverz/REFMET/{mz}/M+H/{tolerance}"
```
---
## MetaboLights Pagination Ignored (Feature-74A)
**Tools**: MetaboLights tools
**Pattern**: `/ws/studies` API silently ignores `size` and `page` params → always returns full list.
**Fix**: Apply pagination client-side after fetching all results:
```python
all_studies = resp.json()
start = page * size
return all_studies[start:start + size]
```
---
## ChEBI Search Nulls and HTML (Feature-74A)
**Tools**: ChEBI_search_compounds
**Pattern**: `smiles` and `inchikey` always null from search API. Names contain HTML entities (`&`, `<`).
**Fix**: Strip HTML from names using `re.sub(r"<[^>]+>", "", name)` and `html.unescape(name)`. Don't return null smiles/inchikey — omit them or fetch from detail endpoint.
---
## HMDB No Open API (Feature-74A)
**Tools**: HMDB tools
**Pattern**: HMDB has no open REST API; tools were returning stub success responses.
**Fix**: Return `{"status": "error", "message": "HMDB does not provide a public REST API. Use MetabolomicsWorkbench or ChEBI instead."}`.
---
## PharmGKB relatedGenes Filter (Feature-74B)
**Tools**: PharmGKB tools
**Pattern**: `relatedGenes.id` and `relatedGenes.symbol` filters not supported by API — silently returns all results.
**Fix**: Return informative message: `"relatedGenes filter is not supported by PharmGKB API. Filter results client-side or use gene-specific endpoints instead."`