Generated from
CONTRIBUTING.mdOn this page24
Contributing to nf-claw
Contributions are welcome: new pipelines and handoff rules, runner features and fixes, tests and documentation. This page covers how to report a problem, set up a development checkout, add a pipeline or a handoff rule, test, cut a release and write documentation.
Navigation
Principles • Reporting bugs • Proposing a feature • Drift and upstream defects • Development workflow • Code conventions • Adding a pipeline • Adding a handoff rule • Tests • Cutting a release • Writing documentation
Principles
- No pipeline-specific guesses. The runner (
runner/) and the maintenance code (librarian/) take their behaviour from the pinned schemas and the handoff rules, not from assumptions about a pipeline's name or columns. The few exceptions (runner/fastqrepair_compat.py,runner/source_corrections.py,runner/demo_defaults.py,runner/path_roles.json,runner/input_roles.json,runner/param_roles.json) need evidence from the pinned source and a check that they apply only to the release they were written for. Runtime corrections record that evidence for the replay. New compatibility code must be just as explicit. - Reproducible generated files. Generating the Markdown and JSON files twice from the same commits must give identical bytes. Generated files contain no timestamps; commit hashes identify versions.
- A test for every change. Every fix and feature comes with a regression test: write the failing
test first, make the smallest change that fixes it, and run
make test. - No drift. The committed
skill.md,reference.mdandcatalog.*must always match the pinned submodules. The drift gate (python3 -m librarian.check_drift) rejects any difference.
The README maps the repository, and docs/architecture.md
explains how its parts (pipelines/, handoffs/, runner/, librarian/) fit together.
Reporting bugs
Do not report a suspected security vulnerability in a public issue; report it privately as SECURITY.md describes.
Which kind of problem is it?
| Kind | Typical symptoms | Where it goes |
|---|---|---|
| Host or environment | Spaces in a path, missing Python packages, JVM socket timeouts on IPv6-only hosts, Docker permissions, running out of memory on a workstation. | Check docs/known-issues.md. Fix it with flags (--nxf-env, --limit-*, --config) or in the system configuration. |
| Upstream pipeline defect | A task fails inside an nf-core process (.command.err), a container image is broken, test profiles conflict. | The pinned source is not edited. Document the workaround in docs/known-issues.md and report the bug to the pipeline's nf-core repository. |
| nf-claw defect | Preflight rejects a valid value, a lock never releases, a provenance bundle is broken, a chain fails. | Open an issue on GitHub. |
What to include
- A title that names the part affected, e.g.
[runner],[chain]or[librarian]. - Your environment:
uname -a,python3 --version,nextflow -version, anddocker --versionorapptainer --version. - How to reproduce it: the exact command, the pipeline and version (
nfclaw versions <name>), and the smallest samplesheet or parameters that show the problem. - Logs:
<outdir>/provenance/logs/run.logand<outdir>/provenance/run_manifest.json, and the relevant parts of<outdir>/.nextflow.logand the failing task's.command.err.
Proposing a feature
Open an issue before a large pull request: describe what the feature does, the workflow it supports and why. A feature should:
- work for any pipeline, reading scientific inputs and parameters through the upstream schemas and the handoff rules rather than assumptions about a pipeline's name;
- keep what an agent has to read short;
- give the same, checkable result on every machine.
Discuss where it fits (runner/, librarian/ or the CLI) before implementing it, and include
tests and documentation with it.
Drift and upstream defects
If python3 -m librarian.check_drift reports drift:
- Missing or stale skills or catalog: run
make buildto regenerate them from the pinned submodules. - Registrations disagree:
sources.tsv,.gitmodulesand the directories inpipelines/must list the same pipelines with the same remote URLs. - A handoff rule no longer fits: if an upstream pipeline renamed an output or a downstream
pipeline changed its samplesheet schema, update
handoffs/<upstream>/<downstream>.jsonto fit both.
Never edit files in pipelines/<name>/upstream/: the submodules point at upstream release commits
and stay as published. Work around an upstream defect with a narrow runtime mechanism
(recorded corrections, a -c config) or document it in
docs/known-issues.md.
Development workflow
Setup
Clone into a path without spaces (e.g. /home/user/nf-claw); on macOS, outside iCloud folders.
git clone https://github.com/<your-username>/nf-claw.gitcd nf-clawgit submodule update --initpython3 -m venv .venvsource .venv/bin/activatepip install -e ".[dev,yaml]" # nfclaw with the test, lint and YAML extrasBranches and commits
Work on a branch named after the change (feat/memory-limit-flag, fix/lock-release) and write
Conventional Commits:
feat:a new feature (feat(runner): add a memory limit flag)fix:a bug fix (fix(chain): release the lock after a failed stage)docs:documentation onlytest:tests onlychore:maintenance, dependency updates and workflow changes
Before opening a pull request
make testpasses.python3 -m librarian.check_driftreports no drift.ruff check .passes, as in CI.- If you changed documentation or data files,
pytest tests/test_agent_docs.pychecks the links and anchors. Then build and test the website (Node.js 24, as in CI):The browser tests run an axe-core accessibility check on every page. Each Markdown guide directly inbashnpm --prefix website cinpm --prefix website run typechecknpm --prefix website run buildnpx --prefix website playwright install chromium # oncenpm --prefix website run test:browserdocs/becomes a page (the audit records and evidence do not), and a skipped heading level or a table without a header row fails that check. - Note any user-visible change (a command, flag, output, documented behaviour or fix) in
CHANGELOG.mdunder Unreleased, with the pull request's number.
Pull requests
Describe the problem and how the change solves it, and link related issues (Fixes #…). Every
check on the pull request must pass: tests (pytest on Linux and macOS, website,
nextflow-integration), drift-check and CodeQL, plus the real-run validations that a change to
runner/ or to their own files triggers (demo-validation, fastqrepair-validation;
pipeline-execution runs when its own workflow changes).
Code conventions
- Python 3.11+ typing:
from __future__ import annotations,str | None,list[str],dict[str, Any]. - Use
pathlib.Pathfor paths, never string concatenation. - Never run a shell string (
shell=True); passsubprocess.runorsubprocess.Popenan argument list, so nothing is split or interpreted by a shell. - Background processes must handle
SIGINTandSIGTERMand stop their child tasks. - Path checks belong in their modules: repository, output and work paths in
runner/preflight.py(--allow-spaceslifts the space check, line breaks are always refused), input file names and routes inrunner/inputs.py, and the separation of a run's writes from its inputs inrunner/input_safety.py. - Lint with Ruff, configured in
pyproject.toml:ruff check .. Do not add dependencies the code does not need.
Adding a pipeline
Only DSL2 pipelines can be added: Nextflow removed DSL1 in 22.12.
1. Check the name
A name may contain only ASCII letters, digits, dots, hyphens and underscores, and must start and end with a letter or digit:
^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$| Name | Valid? | Why |
|---|---|---|
rnaseq | yes | Letters only. |
viral-recon_v2 | yes | Letters, digits, a hyphen and an underscore. |
../evil | no | A path traversal. |
.hidden | no | Starts with a dot. |
2. Register it in sources.tsv
Add a tab-separated row:
<name> https://github.com/nf-core/<name>.git latest-releaseThe remote must be the pipeline's nf-core repository. Use the latest-release policy; hold keeps a
pipeline at its current pin (update policy).
3. Add and pin the submodule
git submodule add https://github.com/nf-core/<name>.git pipelines/<name>/upstreamgit -C pipelines/<name>/upstream fetch --tagsgit -C pipelines/<name>/upstream checkout tags/<X.Y.Z> # a published release, e.g. 1.2.0[!NOTE] Pin a release tag, never
main,masterordev. A submodule left on a branch makes the generated files record a branch commit instead of a release.
4. Generate its files
make buildThis writes pipelines/<name>/skill.md and pipelines/<name>/reference.md and rebuilds
catalog.json and catalog.md.
5. Check
make checkThe drift gate and the full test suite must both pass.
Adding a handoff rule
Each rule is one file, handoffs/<upstream>/<downstream>.json.
Find the output paths in the upstream pipeline's docs/output.md and in the code that publishes
them, and the columns the downstream parameter needs in its input schema. Some parameters use an
auxiliary schema; when its contract disagrees with a bundled template, read the pinned parser. The
drift gate checks parameters and column compatibility; the published files themselves are checked
when the handoff runs. The rule format (source types, build templates and samplesheet
transformations, with handoffs/fetchngs/rnaseq.json as the example) is in
docs/chaining.md.
python3 -m librarian.check_drift # the rule fits both pipelines' schemasmake build # rebuild the catalognfclaw chain run spec.json --outdir /tmp/test-chain --checkTests
Every bug fix needs a regression test:
- Write a test in
tests/that reproduces the bug or describes the new behaviour. - Check that it fails on the current code.
- Make the smallest change that fixes it.
- Check that it passes.
make test # the full suite, one test at a timepytest -n auto --dist worksteal # the same suite on every core, as CI runs itpytest tests/test_chain.py # one modulepytest -k "test_verify" # tests matching an expressionMake targets
| Target | Runs | What it does |
|---|---|---|
make add | librarian.add_pipeline | Prints the .gitmodules entries and git submodule add commands for every pipeline in sources.tsv; changes nothing itself. |
make build | librarian.write_skill --all, then librarian.write_catalog | Regenerates the skills, references and catalogs from the pinned submodules. |
make update | librarian.update_pipelines, then make build | Finds the newest release tags, bumps the submodules and regenerates the files. |
make check | librarian.check_drift, then pytest | The drift gate, then the full test suite. |
make test | pytest | The full test suite. |
Cutting a release
A release is an annotated tag vX.Y.Z on main. It fixes the runner and every pipeline pin at that
commit, while main keeps moving as the maintenance workflows bump pins. Between releases, main
keeps the last release's version, and nfclaw --version adds the commit.
- In one pull request, rename Unreleased in
CHANGELOG.mdto## [X.Y.Z] — YYYY-MM-DDunder a new, empty Unreleased, and setversioninpyproject.tomlandversionanddate-releasedinCITATION.cffto match.tests/test_version.pychecks that the three agree. - Merge it once every check is green, the macOS pytest lanes included: the
mainruleset requires only some of them. - Tag the merge commit (
git tag -a vX.Y.Z -m "nf-claw X.Y.Z" <commit>) and push the tag. - Publish the GitHub release for that tag: what it contains, how it was validated (link the audit
record and
docs/validation.md), its known limitations, and itsCHANGELOG.mdsection.
Writing documentation
Every behaviour is documented in one place; other documents link to it instead of repeating it.
| Content | Where it goes |
|---|---|
| What an agent or user must do to find, run, follow, replay or chain a pipeline | AGENTS.md, kept short because every agent session loads it. CLAUDE.md imports it (@AGENTS.md), so edit only AGENTS.md. |
| The guarantees behind those steps, and their limits | The guide in docs/ that covers them. A new docs/*.md also needs a row in the README's guide list and an entry in llms.txt. |
| A symptom and its fix | docs/known-issues.md, under the section for that symptom. |
Generated files (skill.md, reference.md, catalog.*) | Never by hand: change the generator in librarian/ and run make build. |
| A user-visible change | CHANGELOG.md, under Unreleased, with the pull request's number. |
| What an audit found, and the evidence | A new dated record in docs/audit/. A record describes its own date: correct the guides, not old records. |
Conventions:
- Write plainly. Say what something does and what it does not do, in short sentences. Leave out promotional words, bold labels standing in for sentences, and decorative diagrams; a list or a table is enough.
- A guide starts with an H1 and a paragraph saying what it covers, then a
Navigationline linking its main sections, and never skips a heading level. The site's accessibility check fails on a skipped level or a table without a header row. - Give the version and date of a measurement ("measured at ampliseq 3.0.0 on 2026-10-15"). The daily
updater moves pins without touching the hand-written guides, so write "pinned" only for a fact that
stays true, and point to
skill.mdfor the current pin. - Say what was run and what it does not establish; a passing check is not biological validation.
- Test a claim about the code against the code.
tests/test_agent_docs.pychecks every relative link and anchor, that every guide is listed, thatdocs/mcp.mdlists every MCP tool, and theCLAUDE.mdimport. Keep such tests independent of the pins and of the number of pipelines: the maintenance gate runs them on every candidate that changes those.