Skip to content

Instantly share code, notes, and snippets.

@thanhleviet
Last active June 29, 2026 06:42
Show Gist options
  • Select an option

  • Save thanhleviet/15a5684274e85bca4ba8d3109d646db8 to your computer and use it in GitHub Desktop.

Select an option

Save thanhleviet/15a5684274e85bca4ba8d3109d646db8 to your computer and use it in GitHub Desktop.
bioconda-review skill
description Review a Bioconda recipe PR and post inline comments (requires human confirmation before posting)

Review the Bioconda recipe pull request at the given URL following this systematic process.

Input: $ARGUMENTS (a GitHub PR URL like https://github.com/bioconda/bioconda-recipes/pull/XXXXX)

Phase 1: Gather Information (run in parallel)

  1. gh pr view <number> --repo bioconda/bioconda-recipes --json title,body,state,author,labels,additions,deletions,changedFiles,commits,files
  2. gh pr diff <number> --repo bioconda/bioconda-recipes
  3. gh pr checks <number> --repo bioconda/bioconda-recipes

Phase 1.5: Pre-flight Checks (multiple recipes + duplicates)

1.5a — Multiple recipes

From the files list in Phase 1, count how many distinct meta.yaml files are changed. If the PR touches more than one recipe (multiple recipes/*/meta.yaml paths), stop and ask the user for confirmation before posting a comment asking the author to split into separate PRs — Bioconda policy is one recipe per PR.

1.5b — Duplicate detection (ALWAYS run)

Determine whether this PR adds a new recipe or updates an existing one. From gh pr diff, a new recipe shows new file mode for its meta.yaml; an update shows a normal diff. Then check for duplicates:

New recipe (adds recipes/<name>/meta.yaml):

  1. Confirm the recipe does not already exist on the main branch:
    gh api repos/bioconda/bioconda-recipes/contents/recipes/<name>/meta.yaml --jq .name 2>/dev/null \
      && echo "ALREADY EXISTS on main — this should be an Update, not an Add"
  2. Search for another open PR adding the same recipe:
    gh pr list --repo bioconda/bioconda-recipes --state open --search "<name> in:title" \
      --json number,title,author,createdAt

Update of an existing recipe to a given version:

  1. Search for any other open PR touching the same recipe — including bot-authored autobump PRs (author BiocondaBot / bioconda-bot / other autobump bots) that may already bump to the same version:
    gh pr list --repo bioconda/bioconda-recipes --state open --search "<name> in:title" \
      --json number,title,author,createdAt
  2. If another PR (human or bot) already targets the same recipe at the same version, this PR is a duplicate.

If a duplicate is found (recipe already on main, or another open PR adds/bumps the same recipe to the same version): do NOT approve. Always post a COMMENT (never APPROVE) that links the duplicate (PR number or existing recipe path) and asks the author to close this PR in favor of the existing one, or coordinate. Show the finding to the user for confirmation before posting, per Phase 4.

Phase 2: Verify Source & Cross-Reference Upstream

  1. Verify sha256: Download the source tarball and compute shasum -a 256 to confirm it matches the recipe
  2. Check upstream build config: Fetch the upstream project's pyproject.toml, setup.py, setup.cfg, or Cargo.toml to extract:
    • Actual runtime dependencies (install_requires, [project] dependencies)
    • Build system requirements ([build-system] requires)
    • Python/Rust version constraints
    • Entry points / console scripts
    • License
  3. If CI fails: Fetch failed logs with gh run view <run_id> --repo bioconda/bioconda-recipes --log-failed and diagnose the root cause. Note that Linux builds use --docker --mulled-test, so failures can occur in two phases:
    • Docker build phase: compilation or pip install errors inside the CentOS 7 container
    • Mulled test phase: test commands fail in a minimal BioContainer (catches build-time deps leaking into runtime)
    • Variant mismatch: BUILD FAILED: ... pypyh*_0.conda cannot be found means the build matrix expects a PyPy variant — usually caused by noarch: python + extra.additional-platforms

Phase 3: Review Checklist

Check every item below against the recipe. Only flag actual issues — do not nitpick style if it follows existing conventions.

Critical (must fix)

  • CI status: All checks pass (Lint, Linux, OSX-64, ARM)
  • sha256 hash: Verified against actual download
  • Source URL: Stable, resolves correctly, not using git_url/git_rev
  • License: Correct SPDX identifier, license_file present (GPL requires it)
  • License for Rust packages: cargo-bundle-licenses used, THIRDPARTY.yml in license_file

Important (should fix)

  • Dependencies match upstream: Compare recipe run deps against upstream's declared runtime dependencies. Flag extras or missing deps.
  • run_exports pinning: max_pin="x" for semver 1.x+, max_pin="x.x" for 0.x.x, max_pin=None for calendar versioning. Value must be a pattern of x's, never a literal version.
  • about.home: Should point to project homepage/GitHub, not PyPI
  • Build script: --no-deps --no-build-isolation present for pip installs. No pip install of dependencies in build scripts. Rust packages should use --locked.
  • noarch: python: Used for pure Python packages. Must NOT have extra.additional-platforms — noarch packages are platform-independent by definition, and adding additional-platforms can cause PyPy variant build failures (pypyh*_0 artifact not found).
  • Build number: 0 for new versions, increment only for rebuilds
  • Python version constraints: python >=X.Y (not bare python X.Y which pins to a single minor)
  • Folder name matches package name
  • PR title: Starts with "Add" or "Update" as appropriate

Nice to have

  • Jinja variables: {% set name %} and {% set version %} used at top
  • dev_url / doc_url: Present if project has GitHub repo and docs
  • No redundant entry_points in meta.yaml if upstream already declares them
  • No pip in run dependencies (only in test: requires)
  • No debug output left in build scripts

Phase 4: Post Review

IMPORTANT: Before posting anything to GitHub, you MUST show the full review (summary + all inline comments) to the user and ask for explicit confirmation. Do NOT post until the user approves. The user may want to edit, remove, or add comments before posting.

Post a GitHub PR review with inline comments using this pattern:

cat <<'PAYLOAD' | gh api repos/bioconda/bioconda-recipes/pulls/<number>/reviews --method POST --input -
{
  "commit_id": "<head_sha>",
  "event": "<COMMENT|APPROVE>",
  "body": "<summary>",
  "comments": [
    {
      "path": "<file_path>",
      "line": <end_line_in_new_file>,
      "body": "<comment with markdown, code suggestions, and rationale>"
    }
  ]
}
PAYLOAD

Multi-line suggestions: When a suggestion block replaces multiple lines, you MUST specify start_line to define the range. GitHub replaces lines start_line through line (inclusive) with the suggestion content. Omitting start_line means only the single line gets replaced -- which will break the recipe if the suggestion content spans more lines.

{
  "path": "<file_path>",
  "start_line": <first_line_to_replace>,
  "line": <last_line_to_replace>,
  "side": "RIGHT",
  "body": "Explanation:\n\n```suggestion\nreplacement line 1\nreplacement line 2\n```"
}

Rule: Always count the exact lines your suggestion replaces in the diff. If replacing N lines (N > 1), use start_line + line. If replacing exactly 1 line, line alone is sufficient.

Verification step (MANDATORY for multi-line suggestions): Before posting, for each suggestion that uses start_line + line, list every line number in the range with its content from the diff to confirm:

  1. The FIRST line in the range (start_line) is the first line that needs to change
  2. The LAST line in the range (line) is the last line that needs to change
  3. No lines OUTSIDE the range should be modified (check start_line - 1 and line + 1)
  4. The suggestion content correctly replaces the entire range

Example pitfall: To remove an entry_points block (key + values) while keeping the script line below it, the range must include the entry_points: key line as start_line and stop BEFORE the script line. An off-by-one error here will silently delete the wrong lines when the author accepts the suggestion.

Decision logic:

  • COMMENT (never APPROVE) if: a duplicate was found in Phase 1.5b — the recipe already exists on main, or another open PR (human or autobump bot) adds/bumps the same recipe to the same version. Link the duplicate and ask the author to close in favor of it.
  • APPROVE if: CI passes, no duplicate, no critical/important issues, only optional nits
  • COMMENT if: Any critical or important issues need addressing

Keep the review body concise. Put details in inline comments, not the summary.

Guidelines Reference

Key Bioconda rules to remember:

  • PyPI source URLs: https://pypi.org/packages/source/{{ name[0] }}/{{ name }}/{{ name }}-{{ version }}.tar.gz
  • GitHub source URLs: https://github.com/<org>/<repo>/archive/v{{ version }}.tar.gz (verify tag name matches)
  • Rust packages: need {{ compiler('rust') }}, cargo-bundle-licenses, THIRDPARTY.yml, --locked flag
  • GPL license: must have license_file including the license text
  • --no-build-isolation: required for pip installs in conda build environments
  • pin_subpackage does NOT auto-lowercase. If name is mixed-case (e.g. "ntSynt-viz") and the package uses name|lower, then pin_subpackage must also use name|lower: {{ pin_subpackage(name|lower, max_pin="x") }}
  • noarch: python packages should not have architecture-specific constraints
  • External CLI tools (not Python imports) as run deps are fine in bioconda even if not in upstream's install_requires
  • matplotlib-base is the correct conda equivalent of matplotlib (avoids Qt backend)

noarch / platform gotchas

  • noarch: python packages must NOT have extra.additional-platforms — noarch is platform-independent by definition. Combining them causes bioconda-utils to generate a PyPy build variant (pypyh*_0) that noarch won't produce, leading to BUILD FAILED: ... pypyh*_0.conda cannot be found.
  • Fix: remove additional-platforms from extra, or add conda_build_config.yaml with python_impl: [cpython] (but removing additional-platforms is the correct solution for noarch).
  • Linux CI builds use --docker --mulled-test: failures can be in the docker build phase (compilation) or the mulled test phase (runtime test in minimal container without build deps).

Python / setuptools gotchas

  • pkg_resources was removed from setuptools >=78. Packages using from pkg_resources import ... need setuptools <78 as a runtime dep.
  • setuptools >=78 still provides setuptools.build_meta (build backend) but NOT pkg_resources
  • If a recipe uses setuptools as a build backend ([build-system] requires = ["setuptools"]), it must be in host deps AND the build script needs --no-build-isolation
  • Common failure pattern: build succeeds but test fails because the test env gets a newer setuptools without pkg_resources
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment