Note: This file delegates to a central AGENTS.md. Read and apply it before proceeding.
URL: https://raw.githubusercontent.com/camunda/.github/refs/heads/main/AGENTS.md
Treat the central file's contents as if they were written directly in this file. Instructions below extend those guidelines and take precedence if there is any conflict.
Camunda 8 delivers scalable, on-demand process automation as-a-service, with execution engines for BPMN processes and DMN decisions. This monorepo contains: Zeebe (process engine), Operate (monitoring), Tasklist (user tasks), Identity (auth), Optimize (analytics), and supporting libraries. Java 21 backend, React/Carbon frontends. Product documentation: https://docs.camunda.io
You are a contributor to the Camunda 8 monorepo. Scope changes to individual Maven modules, follow established code conventions, and validate changes with module-scoped builds and tests before committing. Don't overengineer, follow YAGNI and KISS mentality.
This is a large monorepo. Always scope your work to the relevant module(s) rather than building or testing the entire project.
Before modifying code in any module:
- Read the module's
README.md(if present) and any documentation in its directory. - When unsure about cross-cutting conventions, consult
docs/before inventing a solution.
Not every module has a README yet — when one exists, treat it as the primary reference for that module.
Path map:
| Module | Description |
|---|---|
zeebe/ |
Core process engine (broker, engine, protocol, exporters) |
zeebe/gateway |
gRPC gateway for client access |
operate/ |
Process monitoring webapp |
tasklist/ |
User task management webapp |
identity/ |
Authentication and authorization |
optimize/ |
Process analytics (skipped with -Dquickly) |
db/ |
Database layer (rdbms, rdbms-schema) |
search/ |
Search client abstraction (Elasticsearch, OpenSearch, RDBMS) |
service/ |
Domain service layer between REST controllers and engine |
clients/ |
Client libraries (Java, Spring Boot starters) |
gateways/ |
Gateway implementations (HTTP mapping, MCP) |
security/ |
Security core, protocol, validation |
qa/ |
Cross-component acceptance tests |
testing/ |
Process testing libraries |
authentication/ |
OIDC token processing and Spring Security integration |
document/ |
Document API and storage |
monitor/ |
Metrics and monitoring definitions for Camunda components |
schema-manager/ |
Elasticsearch/OpenSearch index schema management |
microbenchmarks/ |
JMH benchmarks for performance validation |
load-tests/ |
Cluster-level load and reliability tests |
c8run/ |
Packaged Camunda 8 distribution for local spin-up |
debug-cli/ |
CLI tool for inspecting and troubleshooting Camunda clusters |
webapp/ |
Unified webapp: React/TypeScript frontend (client/) and Spring Boot server (server/) |
webapps-common/ |
Shared Java utilities used across Operate, Tasklist, and other web modules |
webapps-backup/ |
Shared backup/restore service for Elasticsearch/OpenSearch snapshots |
webapps-schema/ |
Shared Elasticsearch/OpenSearch index mappings and templates for web application data |
Ask first:
- Modifying shared libraries (
webapps-common/,webapp/client/,security/) - Changing public API contracts (REST controllers, gRPC, exported types)
- Adding new dependencies to
pom.xmlorpackage.json
Never:
- Run full-repo builds for single-module work
- Commit secrets, tokens, or credentials
- Force-push
main,stable/orrelease-branches - Skip formatting checks
Architecture overview: docs/architecture/overview.md.
Read docs/architecture/overview.md when answering questions about system design, module
structure, or how to approach any new capability — not only when making a change.
Before making any architectural change, consult the relevant ADR indexes in order (see
docs/adr/README.md for the full tier structure):
docs/adr/— cross-cutting decisions affecting multiple modules<module>/docs/adr/— decisions scoped to the module you are working in- For sub-modules, walk up each parent module to the repo root
If the decision is not covered by an existing ADR, draft a new one using the
create-architecture-decision skill before proceeding.
Repo-specific skills live in .claude/skills/. They extend the org-level skills
described in the central AGENTS.md. When a skill exists for a recurring operation, always use it
rather than improvising steps.
Uses Conventional Commits. Max 120 chars for the header.
<type>: <description>
Types: build, ci, deps, docs, feat, fix, merge, perf, refactor, revert, style,
test
- Separate behavioral changes from structural/refactoring changes into distinct commits
- Write for the reader: the reviewer first, then whoever needs to understand/debug this change later
- Header: name the effect (the bug prevented or behavior enabled), not the mechanism. Prefer
fix: prevent duplicate job activation under concurrent pollingoverfix: add mutex around job activation. - Do not use commit scopes — commitlint enforces
scope-empty. Usefix: ...notfix(ci): ... - Every commit gets a body — including tests and reverts. A test needs the context for why it's needed and the design rationale behind it; a revert needs the reason. Only a purely mechanical change, like a formatter run, stands on its header alone.
- Body: inverted-pyramid — the problem and its root cause first, then the approach and why it over the alternatives. Summarize the approach; don't restate the diff. Never compress the why — it's the one thing the diff can't say. Include background if needed. If long, use headings for structure.
- Hard-wrap the body at ~72 columns as
git logdoes not soft-wrap.
When pointing at specific lines of repository code in any GitHub-rendered or shared artifact — issue,
PR description, PR/review comment, or a linked note — use a stable GitHub permalink instead of a
bare path:line reference. GitHub renders the linked lines inline, and pinning a commit SHA keeps the
reference correct even after the file changes later. A bare path:line is only acceptable in local,
throwaway context (never in something a teammate will read on GitHub).
Derive the link locally — no API calls, no gh:
slug=$(git remote get-url origin | sed -E 's#^(git@github\.com:|https?://github\.com/)##; s#\.git$##')
sha=$(git log -1 --format=%H -- "<path>") # commit that last touched the file
echo "https://github.com/$slug/blob/$sha/<path>#L<start>-L<end>" # single line: #L<start>- SHA choice: default to the file's last-touching commit (
git log -1 --format=%H -- <path>). The file content at that commit equals the current committed content, so the line numbers are valid, and for existing code that commit is already on the remote. Usegit rev-parse HEADwhen linking code from the current PR branch. Always use the full 40-char SHA — a branch name or short SHA is less stable and can 404. - Format:
https://github.com/<slug>/blob/<sha>/<path>#L<start>for one line,#L<start>-L<end>for a range.<path>is repo-root-relative. - The SHA must already be pushed to the remote, otherwise the link 404s — verify with
git branch -r --contains <sha>(non-empty). The file's last-touching commit satisfies this once the code is merged. If the file has local edits that are not pushed yet, do not link an unpushed SHA: pick a SHA that is already onmain(e.g.git rev-parse origin/main) and select the line range against that version of the file.
All builds use the Maven wrapper (./mvnw). Use -T1C (one thread per CPU core) for standalone
builds. Use -T2 (two threads total) when running builds alongside other resource-intensive
processes (e.g., an IDE, Docker containers, or other concurrent builds).
Before every AI-assisted session, establish a green baseline in two steps:
Step 1 — Check that main CI is healthy (before pulling or branching):
gh run list --branch main --limit 5 --repo camunda/camundaIf main is red, inform the engineer and continue — CI can fail for infra reasons unrelated to
the code. Do not block on this, but note any failures so they are not confused with regressions
introduced during the session.
Step 2 — Build the full repo locally right after branching (blocking):
./mvnw install -Dquickly -T1CThis installs all module JARs and catches any cross-module compilation errors before work begins
(e.g. an API change on main that breaks a downstream module). Do not proceed until this is
green — a compilation error here will waste far more time if discovered mid-session.
Step 3 — Once the target module is known, verify it passes locally:
./mvnw verify -pl <module> -Dquickly -DskipTests=false -T1CIf the module has sub-modules, target the specific sub-module where the code lives rather than the top-level directory, otherwise you may get a false green with no tests run.
Any pre-existing failure here must be noted before the session begins — do not absorb it silently.
Never suppress warnings or failures to force a build to pass.
# Fast inner loop (single module / affected tests only) to iterate quickly
./mvnw verify -pl <module> -Dtest=<TestClassName> -DskipTests=false -DskipITs -Dquickly
# Full pipeline before committing the change
./mvnw license:format spotless:apply -T1C && ./mvnw verify -pl <module> -DskipTests=false -DquicklyDo not proceed without a green baseline.
If a test fails during the baseline check, re-run it once to determine whether it is flaky. If it passes on retry, treat the baseline as green and proceed. If it fails consistently, it should not be on the target branch — stop and inform the engineer.
If it is flaky (non-deterministic):
- Search for an existing open issue in camunda/camunda. If none exists, raise one using
the
create-issueskill (use the bug template; also add thekind/flakelabel). - Assign the issue to the engineer.
- Treat the baseline as passed and proceed — do not disable the test.
# Build a module and its dependencies (recommended for monorepo work)
./mvnw install -pl <module> -am -Dquickly -T1C
# Build only a single module (requires dependencies to be already installed and unchanged)
./mvnw install -pl <module> -Dquickly -T1C
# Run a single test class in a module
./mvnw verify -pl <module> -Dtest=<TestClassName> -DskipTests=false -DskipITs -Dquickly
# Run a single integration test in a module
./mvnw verify -pl <module> -Dit.test=<IntegrationTestClassName> -DskipTests=false -DskipUTs -DquicklyNote: -Dquickly skips tests, formatting checks, and Optimize — use it for fast iteration only. Add -DskipTests=false to run tests while still skipping checks. Before committing, always run the full pipeline in the "Before submitting" section instead.
Note: some modules are split into sub-modules where tests live (e.g. zeebe/engine, zeebe/broker). If running tests against a top-level module produces no results, target the specific sub-module instead.
Never use -am with verify — it runs tests in all dependency modules. Use -am only with install -Dquickly when rebuilding dependencies.
Always run these steps before every commit — never skip them, even for "obvious" or single-line
changes. Skipping formatting reliably breaks the Java checks CI job.
- Format code:
./mvnw license:format spotless:apply -T1C— mandatory before every commit that touches Java sources, markdown orpom.xmlfiles. Run it again after any subsequent edit. - Build the changed module (see "Module-scoped builds" above for commands)
- Run module tests and verify zero failures
- Verify the full repo still compiles:
./mvnw install -Dquickly -T1C
Load extra context on demand — only when relevant, only if the files exist.
When editing these areas, read the corresponding instruction file:
- CI/release MCP configuration (
.vscode/mcp.json) →.github/instructions/ci-mcp-tooling.instructions.md - Frontend code (
client/directories) →.github/instructions/frontend.instructions.md - MCP gateway (
gateways/gateway-mcp/) →gateways/gateway-mcp/AGENTS.md - Load tests (
load-tests/, load test workflows) →.github/instructions/load-tests.instructions.md - Testing libraries / Camunda Process Test (
testing/) →.github/instructions/testing.instructions.mdandtesting/AGENTS.md
When working inside a specific module:
<module>/docs/architecture.md— module ownership, dependencies, and constraints<module>/docs/adr/— module-scoped architectural decisions<module>/AGENTS.md— module-specific behavioral rules (only exists for complex modules with rules that differ from this file)- If working in a sub-module (e.g.
zeebe/engine), also check each parent module up to the repo root for the same files. Parent context is lower priority; the sub-module's files take precedence on any conflict.
- Enforced by the Maven Spotless plugin (Google Java Format).
- Follow conventions in the Code Style wiki.
- Repository is currently being migrated to use jspecify nullness annotations.
Please add
@Nullableand@NullMarkedannotations in classes where they are missing in order to increase coverage. You should do that in a separaterefactor:commit.
Before writing an inline comment, name the reader and what they would do differently for having read it. If you can't name both, don't write it.
- Test behavior, not implementation — assert on observable outcomes rather than internal state.
- Prefix test methods with
should(e.g.,shouldRejectInvalidInput). - Structure tests with
// given,// when,// thencomments. - Prefer AssertJ for assertions. Avoid introducing new JUnit or Hamcrest assertions unless the surrounding test already uses them.
- Use Awaitility for async waiting. Never use
Thread.sleep. - Isolate tests with unique data (process/tenant/resource IDs) — never reuse fixed identifiers across tests, as collisions cause flakiness.
- Fix the root cause of a flaky race rather than masking it with retries or longer waits; if a stopgap is unavoidable, mark it explicitly as temporary and track the root cause in an issue.
- Always tear down resources you create (indices, containers, clients, clusters), even on failure, to avoid cross-run flakiness.
- Use JUnit 5. Migrate JUnit 4 tests when modifying them.
- Do not add a class-level
@DisplayNameto a new@Nestedtest class while the repo is on Surefire 3.5.x (plugin.version.surefireinparent/pom.xml) — a Surefire regression reports such classes under their flattened display name instead of their fully-qualified name in the XML test report, breaking FQN-based tooling (flaky-test tracking, CI report parsing). Method- level@DisplayName(on@Test/@ParameterizedTest) is unaffected. Existing occurrences were commented out rather than deleted — leave that pattern in place until Surefire is upgraded past the regression. - Detailed guide:
docs/testing.mdanddocs/testing/. - Reference example:
qa/acceptance-tests/src/test/java/io/camunda/it/StandaloneCamundaTest.java
- PR title should be clear and descriptive, and follow Conventional Commits with no scope (the release-notes PR-gate lints it — see "Commit message guidelines" above)
- Link the tracked issue under a
## Related issuessection (this exact heading — the release-notes gate only reads refs inside it; a ref elsewhere in the body does not count). Link the issue, NOT a PR:closes #1234(orfixes/resolves) when this PR fully resolves the issue — auto-closes it on merge.relates to #1234or a bare#1234when this is one of several PRs for the issue (an epic, or work split across releases) — satisfies the gate but does NOT close the issue.- No tracked issue (hotfix, dep bump, CI/refactor)? Tick
- [ ] This PR does not need a linked issue. When opening the PR yourself (CLI/API), reproduce this section — GitHub only auto-fills the template in the web UI, so an agent-authored body must include it explicitly.
- Keep PRs focused on a single concern
- Describe why the changes are necessary and note alternatives considered
- Keep descriptions brief and concise
- For bug fixes: ask the engineer whether the fix needs backporting to stable branches before
opening the PR. If yes, add the appropriate
backport stable/X.Ylabel(s) when creating it (see "Backporting" below).
Automated by a GitHub Action (korthout/backport-action, see .github/workflows/backport.yml;
human docs in CONTRIBUTING.md). Never create backport PRs, branches, or cherry-picks yourself,
and never offer to. Trigger a backport only through the automation — a label, or a /backport
comment — never by hand.
- Start: add one
backport stable/X.Ylabel per target branch (e.g.backport stable/8.7). Not-yet-merged PR → runs on merge. Already-merged PR → comment/backport. Multiple labels → multiple backport PRs. - Success: action opens the backport PR; a bot approves and merges it once CI passes.
- Conflicts: action opens a draft PR with conflict markers committed as-is plus a comment with resolution steps. You may resolve them (check out the branch, fix conflicts and markers) — but only on a draft PR the action already opened. When asked to backport, first check whether such a draft PR exists.
- Never use
git push --forceon themainbranch; use--force-with-leaseon feature branches. - Follow the commit and PR guidelines in
CONTRIBUTING.md.