See docs/guide.md for the user-facing handler/client API
and the README for the workspace layout. The current project
maintainers are listed in MAINTAINERS.md.
All commits must be signed off to affirm compliance with the
Developer Certificate of Origin.
No Contributor License Agreement is required. Configure your git identity
to match your GitHub account, then use the -s flag when committing:
$ git commit -s -m "your commit message"- Rust 1.88+ (MSRV; source of truth is
rust-versionin the workspaceCargo.toml— the codebase uses let-chains). protocv27+ — the test and example protos use editions syntax (edition = "2023"). Ubuntu's aptprotobuf-compiler(v21) is too old; install from the protobuf releases page or viaarduino/setup-protocin CI.task generate:alland theconnectrpc-buildconsumers need it.buf— used to drive regeneration of the checked-in code (thebuf.gen.yamlfiles invoke locally-built plugins).task(go-task) — the repo's command runner.task --listshows everything.
Keep each change to ≤ 250 lines net (additions minus deletions,
excluding test files and */generated/*) wherever possible. If a task
naturally exceeds that, split it into focused, self-contained PRs or
commits.
CHANGELOG.md is generated — do not edit it directly. Each change is
recorded as a small fragment file under .changes/unreleased/, so two PRs
never touch the same lines and the changelog never causes a merge conflict.
Add a fragment for any user-visible change:
task changelog-new # prompts for kind (Added/Changed/Fixed/…) and a body
# or, non-interactively:
task changelog-new -- -k Fixed -b "One-line description of the change."This writes .changes/unreleased/<Kind>-<timestamp>.yaml; commit it with your
change. Bodies may span multiple lines and use the same Markdown (`code`,
[#NNN] issue/PR references) as the existing entries. Skip the fragment only
for changes with no changelog impact (internal refactors, test-only edits, CI
tweaks).
The check-changelog CI job regenerates CHANGELOG.md with changie merge
and fails if it differs from what is committed, so a directly-edited or stale
CHANGELOG.md will be caught.
At release time the maintainer rolls the fragments into a version section:
task changelog-batch -- 0.8.0 # fragments → .changes/0.8.0.md (edit for prose)
task changelog-merge # regenerate CHANGELOG.mdEvery change must include unit tests. Target ≥ 80% line coverage for
new code. Reaching 100% is not required when the remaining paths would
require artificial tests, but coverage gaps must be intentional and
justified. Tests live in #[cfg(test)] modules colocated with the code
they test; the tests/streaming crate is the in-workspace e2e consumer
of connectrpc-build.
- Run
task lintandtask testbefore every commit (ortask cito also run docs, minimal-features, and the multiservice example). - Public API items require doc comments (
///); include# Errors/# Panicssections where applicable. - Every
unsafeblock requires a// SAFETY:comment explaining the invariant. - Avoid
.unwrap()outside tests and provably-safe paths. #[inline]only with full-path profiling evidence.
All code generation uses quote! blocks rather than string manipulation;
prettyplease formats the final TokenStream into readable Rust source.
The pipeline mirrors buffa's: accumulate TokenStream → syn::parse2 →
prettyplease::unparse.
Key rules:
- Regular
//comments are not tokens and are silently dropped byquote!. Only doc comments (///→#[doc = "..."]) survive. - Identifiers must go through
format_ident!(orIdent::new_rawfor Rust keywords). Never interpolate raw strings as identifiers. - Type paths resolved from the descriptor context use
rust_path_to_tokens(which wrapssyn::parse_str::<syn::Path>). - Generated code uses fully-qualified paths everywhere
(
::connectrpc::...,::buffa::...) — nousestatements at module scope. This lets multiple generated files beinclude!d into the same Rust module without E0252 collisions.
The Connect protocol conformance suite runs against both server and
client implementations. task conformance:download fetches the runner;
task conformance:build builds the binaries.
| Suite | Task | Tests |
|---|---|---|
| Server default (all protocols) | task conformance:test |
3600 |
| Server Connect-only | task conformance:test-connect-only |
1192 |
| Server Connect+TLS | task conformance:test-connect-tls |
2396 |
| Client Connect | task conformance:test-client-connect-only |
2580 |
| Client gRPC | task conformance:test-client-grpc-only |
1454 |
| Client gRPC-Web | task conformance:test-client-grpc-web-only |
2838 |
A healthy run ends with N passed, 0 failed. CI runs the server-default
and the three client suites.
Six directories contain checked-in buf generate output and must be
regenerated whenever connectrpc-codegen output changes (or the buffa
dependency is bumped):
conformance/src/generated/examples/eliza/src/generated/examples/multiservice/src/generated/benches/rpc/src/generated/connectrpc-health/src/generated/connectrpc-reflection/src/generated/
Regenerate all of them with:
task generate:allThis rebuilds protoc-gen-connect-rust (and the sibling buffa plugins
from ../buffa) in release mode, then runs buf generate in each
directory. CI runs a separate generated-code check that fails when these
checked-in files are stale.
To test combined speculative changes across both repos:
task buffa:link # writes .cargo/config.toml pointing at ../buffa
task buffa:unlink # removes the override; reverts to crates.io / [patch]The override is gitignored and never reaches CI or cargo publish.
The workspace Cargo.lock is committed and CI builds with --locked (the
example scripts and task generate:all add it when CI is set), so a new
release of a dependency never changes what CI builds
until a PR bumps the lockfile. Dependabot opens that PR weekly for
semver-compatible updates (lockfile only; a semver-incompatible upgrade is a
hand-written Cargo.toml change), and the scheduled Latest dependencies workflow
runs the test suite against a fresh cargo update, which is what a crate
depending on connectrpc resolves, so a release that breaks downstream users
is noticed there. The lockfile only governs this
repository's own builds; crates that depend on connectrpc resolve against
the version requirements in Cargo.toml as usual, so keep those floors
truthful.
To bump one dependency: cargo update -p <crate> and commit the lockfile
change. A buffa bump is cargo update -p buffa -p buffa-types -p buffa-codegen -p buffa-descriptor followed by task generate:all, in the
same PR, because the generated-code check builds the buffa plugins from the
tag matching the locked version; the locked version is the regen baseline,
and the buffa requirements in Cargo.toml only move when the new code
needs something the old floor lacks. task buffa:link rewrites the lockfile
to path dependencies; run task buffa:unlink and git restore Cargo.lock
before committing.
GitHub Actions CI (.github/workflows/ci.yml) runs on every push to
main and on all pull requests. Jobs:
- Check —
cargo check --workspace --all-features --all-targetswithRUSTFLAGS=-Dwarnings - Test —
cargo test --workspace - Clippy — workspace, all targets,
-D warnings, on a pinned toolchain (currently 1.95) so new-stable lints don't break CI unannounced - Format —
cargo +nightly-2026-02-27 fmt --check. Nightly is required becauseformat_generated_files = falseinrustfmt.tomlis a nightly-only option; the date is pinned because rustfmt output drifts across releases. Use the same dated nightly locally (rustup toolchain install nightly-2026-02-27 -c rustfmt); bump it together with thefmtjob in.github/workflows/ci.yml - Check generated code — builds the buffa plugins at the version in
Cargo.lock, runstask generate:all, and verifies the checked-in generated directories have no diff - Documentation —
cargo docwith broken-intra-doc-links denied - MSRV —
cargo checkon the minimum toolchain, read fromrust-versionin the workspaceCargo.tomlso the declaration and the check cannot drift - Examples — builds and runs the example crates
- Minimal features —
cargo checkandcargo test, both-p connectrpc --no-default-features. Because the tests run, a test that needsjson,gziporzstdmust be#[cfg(feature = "...")]-gated or it fails here while passing the default-feature suite - Wasm —
wasm32-unknown-unknownbuild of the client example - Latest dependencies (weekly, separate workflow) —
cargo updateto the newest compatible versions, then the test suite - Conformance (server) / Conformance (client) — full suites