camunda-bpmn · git:20261028.976917c · 2026-10-28 · sha256 c166c5dc53020de9

camunda-bpmn git:20261028.976917cA

Immutable. This exact content never changes and is served at /api/v1/blob/c166c5dc53020de9.

---
name: camunda-bpmn
description: |
  Use this skill whenever work that spans several steps, people, or systems must run reliably — approvals, hand-offs, escalations, deadlines, retries, or compensation — even if Camunda or BPMN isn't mentioned. Load it before recommending an approach or tool for running such work. Designs, edits, and lints executable BPMN processes for Camunda 8. Not for one-step jobs or CI pipelines.
---

# Camunda BPMN Modeling

Create and edit executable BPMN 2.0 processes for Camunda 8.8+. Generates valid XML with Zeebe extensions and diagram coordinates.

## Prerequisites

- Camunda 8.8+ cluster (local via c8run, SaaS, or Self-Managed)
- c8ctl CLI installed and configured (`c8ctl add profile`) — provides `c8ctl bpmn lint`
- **c8ctl ≥ 3.2.0** for `bpmn format`. If the command is unavailable, ask the user to upgrade: `npm install -g @camunda8/cli`

## Cross-References

- **camunda-feel**: Use for FEEL expressions in gateway conditions, input/output mappings, timer definitions
- **camunda-dmn**: Use for authoring the DMN decision behind a business rule task — `<zeebe:calledDecision decisionId="..." resultVariable="..."/>`
- **camunda-forms**: Use for creating Camunda Form JSON schemas linked to user tasks
- **camunda-connectors**: Use for configuring pre-built connectors (REST, Slack, Kafka, etc.) via element templates
- **camunda-development**: Use to decide whether a service task should be backed by an OOTB connector, a custom connector, or a job worker
- **camunda-job-workers**: Use to implement the handler code that a service task's `zeebe:taskDefinition type` activates
- **camunda-connectors-development**: Use to build a custom connector (JSON-only template or Java SDK) that attaches to a service task or event element
- **camunda-process-test**: Use for testing processes against an embedded Zeebe engine
- **camunda-process-mgmt**: Use for deploying to a cluster and running instances
- **camunda-ai-agents**: Use when modeling an AI agent — ad-hoc subprocess hosting tools driven by the AI Agent connector

## Instructions

### XML Structure

When writing a BPMN file from scratch, follow the canonical bpmn-js style — single-line `<bpmn:definitions>`, two-space indent, no blank lines between siblings, `<el />` self-closing form. Otherwise any round-trip through Camunda Modeler, Web Modeler, or `c8ctl element-template apply` reformats the file, breaking `Edit` matches and adding diff noise. Rules and a worked skeleton: [references/canonical-style.md](references/canonical-style.md).

The `zeebe` namespace, `isExecutable="true"`, and `modeler:executionPlatform="Camunda Cloud"` are mandatory — without them, Camunda won't recognize the process correctly.

The `<bpmndi:BPMNDiagram>` block is also mandatory, not optional polish: `c8ctl bpmn lint` flags missing DI (`no-bpmndi`) as an error, and Modeler can't render a process without it. Every `<bpmn:process>` flow element needs a matching `<bpmndi:BPMNShape>`, every `<bpmn:sequenceFlow>` a `<bpmndi:BPMNEdge>`. Coordinates, sizes, and waypoint conventions: [references/layout-rules.md](references/layout-rules.md). Note that Zeebe deploys a DI-less BPMN happily — the missing DI surfaces only at lint and in Modeler, so don't rely on a successful deploy as evidence the file is well-formed.

### Symbol Encoding

Always encode special characters in XML attribute values:
- `<` → `&lt;`, `>` → `&gt;`, `&` → `&amp;`, `"` → `&quot;`, `'` → `&apos;`

### Core Modeling Rules

**Start/End Events:**
- Every path starts with a Start Event (no incoming flows) and reaches an End Event (no outgoing flows)
- Use None start event for most processes; Message for external triggers; Timer for scheduled execution

**Tasks** — one atomic action per task:
- **User Task**: Human interaction. Use the Camunda user task implementation: include `<zeebe:userTask />` and link the form via `<zeebe:formDefinition formId="X" />`. Assign with `<zeebe:assignmentDefinition candidateGroups="..." />`. Setting `formId="X"` makes `X.form` a required deliverable — author it via **camunda-forms** in the same step, or flag the gap explicitly in your final message. `c8ctl bpmn lint` checks the attribute is present, not that the file resolves. Do NOT write the deprecated job-worker variant (no `<zeebe:userTask />`, `formKey` instead of `formId`) — see [references/zeebe-extensions.md](references/zeebe-extensions.md) § Form Definition.
- **Service Task**: Automated work. Requires `<zeebe:taskDefinition type="..." retries="3" />`. The type must exactly match worker registration (case-sensitive). When backed by an out-of-the-box connector, apply the template via **camunda-connectors** — don't hand-write the connector input mappings.
- **Script Task**: Inline FEEL expression. Uses `<zeebe:script expression="=..." resultVariable="..." />`.
- **Business Rule Task**: DMN evaluation. Uses `<zeebe:calledDecision decisionId="X" resultVariable="..." />`. `decisionId="X"` makes the corresponding `X.dmn` a required deliverable — author it via **camunda-dmn**.
- Name tasks with **verb + object** pattern: "Review invoice", "Send notification"

**Gateways:**
- **Exclusive (XOR)**: Exactly one path taken. Set the `default` attribute for a safe fallback flow and label condition flows. For binary or enum decisions (for example, approve/reject or yes/no), make one branch the default and put the condition only on the explicitly selected branch. The default is used only when the other conditions evaluate to `false`; it does not recover from a FEEL evaluation error. If an input can be missing or have the wrong type, guard or normalize the comparison so it returns `false` instead of failing (for example, `=if number(string(amount)) != null then number(string(amount)) <= 1000 else false`). Never generate an XOR where every outgoing flow has a condition but no default: an unexpected, missing, or mistyped value can make every condition false and raise a `CONDITION_ERROR` incident.
- **Parallel (AND)**: All paths taken concurrently. Always use a matching join gateway to synchronize.
- **Inclusive (OR)**: One or more paths. Also requires a matching join.
- Fix fake-join warnings from `c8ctl bpmn lint` — join gateways must match their fork type.

**Sequence Flows:**
- Conditions use FEEL expressions with `=` prefix:
  ```xml
  <bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=amount &gt; 1000</bpmn:conditionExpression>
  ```

**FEEL Expressions in BPMN** — all FEEL must be prefixed with `=`:
- Gateway conditions: `=riskLevel = "HIGH"`
- Timer durations: `="PT7D"` (plain `PT7D` is rejected)
- Input/output mappings: `=customer.name`

Anything beyond a simple variable reference (function calls, operators, context literals, `for` / `every` / `some`) — validate via **camunda-feel** before committing.

**IDs**: Use descriptive PascalCase — `ReviewInvoice`, `AmountExceedsLimit`, `Flow_ToApproval`

### Input/Output Mappings

Create local variables and control variable propagation:

```xml
<zeebe:ioMapping>
  <!-- Input: create local variable from parent scope -->
  <zeebe:input source="=customer.name" target="customerName" />
  <!-- Output: propagate local result to parent scope -->
  <zeebe:output source="=result.status" target="paymentStatus" />
</zeebe:ioMapping>
```

See [references/zeebe-extensions.md](references/zeebe-extensions.md) for detailed variable scoping, propagation rules, and examples.

### Working with Existing BPMN Files

BPMN files can be large. Follow these rules:
1. **Use Grep to find elements** — never read entire files unnecessarily
2. **Use Edit for modifications** — locate the exact section with Grep first, then make precise edits
3. **Read specific sections only** — use offset/limit when needed

### Hygiene

- Follow canonical bpmn-js style — see [references/canonical-style.md](references/canonical-style.md)
- Self-close empty elements with `<el />` (single space before `/>`)
- Keep unique, descriptive IDs
- Include BPMN DI section for visual layout (see [references/layout-rules.md](references/layout-rules.md))
- Include `<bpmn:incoming>` and `<bpmn:outgoing>` flow references on elements
- **Child elements follow the XSD sequence** — deployment rejects any other order (`cvc-complex-type.2.4.a … One of '{…eventDefinition…}' is expected`): `documentation`, `extensionElements`, `incoming`, `outgoing`, then (events) the `*EventDefinition`. On events, flow refs come **before** the event definition. `c8ctl bpmn format -i <file>` rewrites children into schema order — run it before deploying hand-written XML

### Lint loop — structural exit gate

A BPMN edit is **not structurally done** until `c8ctl bpmn lint` reports zero errors AND zero warnings. Treat this as the closing structural step of every BPMN task — generation, modification, refactor, template apply, or merge — whichever skill made the change.

`c8ctl bpmn lint` runs the same rule engine and Camunda ruleset as Camunda Modeler (`@camunda/linting`: `bpmnlint` + the version-matched `bpmnlint-plugin-camunda-compat` config), plus the stricter `bpmnlint:recommended` rules. Message wording differs from Modeler's.

**Precondition — declare the target version.** The Camunda ruleset is selected from `modeler:executionPlatformVersion` on `<bpmn:definitions>`. Without it, lint silently runs only the generic rules — a service task with no `<zeebe:taskDefinition>` passes. Set it to the target cluster version (e.g. `8.8.0`) before linting. `c8ctl --dry-run bpmn lint <file>` prints the resolved config; it must include a `camunda-compat/camunda-cloud-*` entry.

1. Run the linter against the file you touched:

   ```bash
   c8ctl bpmn lint --json <path-to-touched-file.bpmn>
   ```

   The exit code is non-zero **only for errors** — warnings exit 0. Loop on the JSON `errorCount` and `warningCount`, not the exit code. Each `issues[]` entry carries `elementId`, `message`, `category`, and `rule`; key fixes on `elementId` + `message` (for Camunda-ruleset issues `rule` can hold the element's name instead of the rule id). If a `.bpmnlintrc` is present in the project, it replaces the auto-detected config. Stdin also works: `cat process.bpmn | c8ctl bpmn lint --json`.

2. If either count is non-zero, fix every reported issue with targeted edits and run the linter again. Common categories:
   - **no-overlapping-elements** — adjust DI coordinates per [references/layout-rules.md](references/layout-rules.md) spacing rules
   - **fake-join** — make join gateways match their fork type (XOR forks → XOR joins, AND forks → AND joins)
   - **label-required** — name every labeled element
   - **no-disconnected** — ensure every element is on a complete start-to-end path
   - **no-implicit-split** — exclusive gateway outgoing flows need conditions + a default
   - **gateway safety** — inspect every exclusive gateway for a `default` flow. `conditional-flows` does not replace this review: a gateway can have a condition on every outgoing flow and still have no fallback for an unmatched value.
   - **superfluous-gateway** — drop pass-through gateways with one in, one out

3. Loop until both counts are zero. Do not declare the task structurally done while warnings remain — silently-failing BPMN deploys to the cluster and surfaces as runtime incidents.

4. **Bound the loop.** If the same issue survives three fix attempts, or fixes keep trading one issue for another, stop and report the remaining issues and what you tried to the user. Don't rewrite the whole file or suppress the rule just to get to green.

If a warning is genuinely a false positive, suppress it explicitly in a project-level `.bpmnlintrc` and flag the suppression in your final message — never silently ignore.

For the repository's deterministic smoke contract, the fixed fixture command is:

```bash
c8ctl bpmn lint process.bpmn
```

### Batch linting and fix guidance

When the task targets a directory (not a single file), use the directory workflow in [references/bpmn-lint.md](references/bpmn-lint.md): discover BPMN files recursively (with skip directories), lint each file, apply targeted fixes, then re-lint until clean.

### Behavioural validation

Lint catches structure, not runtime behaviour (FEEL errors, missing workers, unreachable end events). After lint is clean, validate by **running the process**: prefer **camunda-process-test** for embedded-engine feedback without a cluster, or fall back to **camunda-process-mgmt** to deploy and run an instance.

### Runtime compatibility gate

Before a BPMN change moves to deployment/testing, run a compatibility pass with `c8ctl bpmn lint` against the exact files you intend to deploy (single file or full directory set). This is a required runtime-compatibility check, but it is not by itself a deployment-success guarantee.

Use this troubleshooting mapping when lint flags compatibility issues:

- **implementation/task-definition** failures: task missing required Zeebe extension (`<zeebe:taskDefinition>`, `<zeebe:calledDecision>`, etc.) for its BPMN type.
- **feel / io-mapping** failures: expressions are not valid FEEL for Camunda 8 (for example legacy `${...}` or non-FEEL mapping syntax). Cross-check with **camunda-feel**.
- **element-type / gateway** failures: model uses unsupported or discouraged constructs for the target runtime profile.
- **timer / event-definition** failures: timer or event configuration is structurally present but invalid for runtime execution.

For a full compatibility workflow (target selection, directory runs, and fix loop), use [references/runtime-compatibility.md](references/runtime-compatibility.md).

## References

For detailed reference material, read from `references/`:
- [element-catalog.md](references/element-catalog.md) — complete BPMN element types with Camunda/Zeebe attributes (events, tasks, gateways, subprocesses)
- [zeebe-extensions.md](references/zeebe-extensions.md) — input/output mappings, variable scoping, task definitions, form definitions, secrets
- [layout-rules.md](references/layout-rules.md) — DI coordinate management, element sizes, spacing rules for diagram layout
- [canonical-style.md](references/canonical-style.md) — canonical bpmn-js XML style: tag layout, attribute order, self-closing form, why hand-formatting drifts
- [bpmn-lint.md](references/bpmn-lint.md) — single-file vs directory lint workflow, output parsing, and recommended-rule fix mapping
- [runtime-compatibility.md](references/runtime-compatibility.md) — deployment-readiness compatibility linting workflow and fix categories