# Customising workflows

Source: https://codeherder.com/docs/task-types/

Tailor a workspace's workflows — their stages, gates, fields, and parent rules — from Settings → Workflows or the ch CLI.

Every task carries a type — `story`, `bug`, `feature`, and so on. The type names a **workflow**: the stages the task moves through, the fields it carries, and which parents it can nest under. Every workspace ships with the same seven built-in types described in [Core concepts](https://codeherder.com/docs/concepts/): initiative, epic, feature, bug, story, task, and auto. Workspace owners and admins can create an entirely new type from **Settings → Workflows** in the left sidebar, or from the `ch` CLI. Editing a built-in type takes one extra step first — see *Built-in types: the shared definition and your own copy* below.

## Open the catalog

Navigate to **Settings → Workflows**. The catalog table lists every workflow visible in your workspace:

| Column | What it shows |
| --- | --- |
| **Name** | The type’s display name |
| **Stages** | How many stages are in the pipeline |
| **Parents** | Which types this one can nest under |

Workflows marked **Inherited** come from a parent workspace. They are usable here for creating tasks, but edits must be made in the workspace that owns them. Workspace owners and admins can disable an inherited type for this workspace and its descendants without affecting the parent — see *Disabling an inherited type* below.

Creating, editing, or restoring a version of a type requires a **workspace owner or admin**, on the Starter plan or above — see [Plans and limits](https://codeherder.com/docs/plans-and-limits/#what-your-plan-unlocks). Disabling a type (including an inherited one) needs owner or admin too, but no particular plan. Agents (and members signed in as an agent account) see the same table in a read-only view.

## Create or edit a type

- **Create a new type**: click **+ New type** (owners and admins only).
- **Edit an existing type**: click any row in the catalog table, then click **Edit**.

### Key and label

Every type has a **key** — a short lowercase identifier (for example, `feature` or `sprint-task`). The key must start with a letter and contain only lowercase letters, digits, underscores, and hyphens (`a-z 0-9 _ -`). **The key is permanent**: once a type is created, its key cannot be changed. Choose it carefully.

The **label** is the display name users see when creating a task, and can be updated at any time.

## Stages

The **Stages** section shows the type’s pipeline as an ordered list. This is where you shape the pipeline itself — its order, its branches, and its gates. A stage’s own content — its prompt, required capabilities, and whether it can commit — lives elsewhere; see *Per-stage settings* below for where. For a detailed explanation of what each stage means and how work moves through them, see [How work flows](https://codeherder.com/docs/how-work-flows/).

In the editor you can:

- **Add a stage** — click **+ Add stage** and name it.
- **Reorder** — use the ↑ and ↓ arrows to rearrange the order.
- **Remove** — click ✕ next to a stage.

If you remove a stage that still has live tasks, the app prompts you to choose a surviving stage to move those tasks to before the change is applied.

### Extra transitions

By default, each stage can only advance to the next stage in the pipeline. For each stage you can enable **extra transitions** — non-linear edges to other stages, such as a backward loop that sends a task back for rework or a skip-forward shortcut. Under each stage in the editor you will see an **extra transitions from ‘ ’** panel with a checkbox for every other stage in the pipeline. The implicit forward step to the next stage is always present and shown as permanently checked; only the additional edges appear as editable checkboxes. Backward edges are labelled ↩ to make reject loops easy to identify.

These extra edges define which status transitions are legal for that workflow. The engine only permits a transition if it is on this list (or is a standard operational transition such as blocking or cancelling), so adding a backward loop here is what allows reviewers to send a task back for rework.

### Per-stage settings

A stage’s *content* — what it asks an agent to do, what it requires to run, and whether it can commit — is no longer set in this editor. Where it lives depends on how the type’s pipeline is built:

- **A type composed from the shared stage library — every built-in type, and the common case.** Each stage in the pipeline is an *instance* pointing at a library stage. The stage’s shared content — its prompt and required capabilities — is read-only here: the editor shows a preview, with an **Edit library stage** link to change it at the source. What’s editable per instance in this editor is its name, class, gate, approval, verification routing, transitions, and its own escalation settings — including max re-runs before blocking, which is set per instance here rather than on the shared stage. A composed instance can also turn one of the stage’s own verifications on or off, or change whether it’s required, just for this workflow — that’s a CLI-only overlay, not an editor control; see [Changing a stage’s verifications for one workflow](https://codeherder.com/docs/stage-library/#changing-a-stages-verifications-for-one-workflow) if you want a per-workflow difference instead of forking the whole stage. See [The stage library](https://codeherder.com/docs/stage-library/) for how the library works.
- **A type written out by hand.** Its stages carry their own settings directly, so each one’s prompt, required capabilities, writable switch, and max re-runs before blocking live on the type’s own schema — set through the CLI round-trip described in *Manage workflows from the CLI* below. This editor still controls the pipeline’s *shape*: adding, reordering, and removing stages, extra transitions, required fields, and approval gates.

**Which one am I looking at?** The editor tells you. A composed stage shows a read-only prompt preview and an **Edit library stage** link. A hand-written stage shows only a name box, with a note offering **Convert to composition…**. From the CLI, `ch workspace workflow show --type <type>` prints an `instances (composed from the stage library)` block for a composed type, and no such block for a hand-written one.

Whichever way a type is built, the settings below mean the same thing — only where you set them differs:

**Required capabilities** — a comma-separated list of capability labels (for example, `model:opus`) an agent’s launch config must carry to run this stage. This is a hard staffing gate, not a ranking signal: the engine only runs the stage on a launch config whose own capabilities fully cover this list. A stage that no launch config in the workspace covers is **unstaffable** — any task that reaches it queues indefinitely, with no agent ever picking it up. See **Capabilities and routing** in [Agents and the CLI](https://codeherder.com/docs/agents-and-cli/) for the full explanation, including the separate, softer capability signal that only ranks agents during auto-staffing of unassigned work, or [Capabilities](https://codeherder.com/docs/capabilities/) for the complete picture across every place capabilities appear.

**Writable** — when true, the agent at this stage can commit source code and open merge requests. When false, the stage is planning-only: the agent reads and researches freely but does not commit any changes. See [How work flows](https://codeherder.com/docs/how-work-flows/) for how writable and non-writable stages fit into the default pipelines.

**Max re-runs before blocking** — a number that caps how many times a task can be sent back into this stage from a downstream reject loop. For a composed type it’s a per-instance escalation setting in the editor; for a hand-written one it’s part of the type’s schema, set from the CLI like prompt and required capabilities above. When the cap is reached, CodeHerder automatically moves the task to `blocked` and files a blocker note explaining how many times the stage was re-entered and which stage to resume from; a human then decides whether to continue or cancel. Leaving this at 0 doesn’t remove the limit — it falls back to CodeHerder’s own safety cap instead. Each send-back edge into a stage — `review → code`, `verify → code`, and `merge → code` are the three built into the default types — is tracked independently: rounds on one edge don’t count against another edge’s cap on the same `code` stage. The built-in `feature`, `bug`, `story`, and `task` types all default to 3 on the `code` stage. See [How work flows](https://codeherder.com/docs/how-work-flows/) for the full reject-loop behaviour.

**Stage prompt** — a standing instruction prepended to every agent session that works this stage. Use it to add stage-specific guidance that applies on every run — for example, directing an agent to follow a particular convention or check a specific condition before advancing.

Two further stage-content settings exist — a custom container image, and a setup script that does not need a custom image (direct mode refuses it unless the operator sets `CH_HOST_BEFORE_SCRIPT`) — set the same way as required capabilities and writable above: on the library stage for a composed type, or on the type’s schema for a hand-written one. See [Running a stage in your own container image](https://codeherder.com/docs/stage-images/).

A stage can also be narrowed to a specific list of skills, on a composed workflow — which every built-in type is. See [Which skills a stage gets](https://codeherder.com/docs/stage-skills/).

## Approval gates

The toggle is available on every stage, but it only takes effect on a stage CodeHerder can leave on its own — for the built-in feature, bug, and story types, that’s the `todo` stage a task starts at, before any agent has picked it up. Turning it on for a stage an agent or reviewer leaves explicitly (`plan`, `code`, `review`, `merge`, `verify`) has no effect, since those hand-offs are never gated. For each stage, tick **approval required to leave ‘ ’** and then configure who must approve:

- **By kind** — any member of kind `human` or `agent`.
- **Specific member** — pick an individual workspace member from the dropdown.

You can also enable **requester can’t self-approve** to enforce separation of duties: whoever approves must be a different identity than whoever requested the advance.

See [Approvals & staying in control](https://codeherder.com/docs/approvals/) for how the approval flow works: the pending-advance state, how to approve or reject, and how to find tasks waiting on you.

A stage’s gate can also carry an optional change-shape limit — capping how many files, lines, or top-level directories a task’s change can touch before it’s refused when leaving that stage. It’s off by default and isn’t in this web editor at all, for a composed or a hand-written type alike; you set it from the CLI. See [Change shape](https://codeherder.com/docs/change-shape/) for what it measures and the full setup.

## Custom fields

The **Fields** section lists the type’s custom fields. Each field has:

- **Key** — a short identifier, used when filling the field with `ch task edit <taskId> --<key>` (see [Editing and cancelling tasks](https://codeherder.com/docs/editing-tasks/#when-a-field-can-be-written)). It must be lowercase letters, digits, and underscores, starting with a letter, up to 64 characters, and it can’t reuse the name of a column every task already has — title, description, priority, due date, status, type, parent, or cost budget. A key that happens to match one of the CLI’s own global flag names still works, just not as a `ch task edit` flag — set it with `ch task field <taskId> <key> --value <text>` instead.
- **Label** — the display name shown in the web app.
- **Type** — `markdown` (free-form text), `checklist`, `child-tasks` (a list of linked subtasks), or `prototype`. You can pick all four when you add a field here.
  - `checklist` is a task-list value (`- [ ]` / `- [x]`). It renders as tickable checkboxes. On a required field, it blocks the task from advancing to `merge` or `done` until every item is ticked. The built-in **Acceptance criteria** field on `story` and `feature` tasks uses it. See [Writing tasks an agent can build](https://codeherder.com/docs/writing-tasks/) for how to write and tick a checklist field.
  - `prototype` holds the ID of one UI prototype. The prototype must be a ready `.html` attachment on the same task. Any other value is refused. Filling it takes two steps. First run `ch task attachments create <taskId> <file.html> --prototype` to upload the file. Then run `ch task edit <taskId> --prototype <attachmentId>` to set the field. The upload does not set the field by itself. On a required field, an empty value blocks the task from advancing with the message “prototype is not attached”. A reused task does not copy the value. See [Attachments](https://codeherder.com/docs/attachments/) for the upload options.
- **Required** — marks the field as required.

Click **+ Add field** to add a new field, or **Remove** to delete one.

## Allowed parent kinds

The **Allowed parent kinds** section controls which other types this type can nest under. Check one or more parent types, or leave all unchecked to make the type top-level only.

## Disable and restore

### Locally-owned types

To retire a type without losing historical data, click **Disable** on the type’s detail page. A disabled type:

- is hidden from new-task creation,
- leaves all existing tasks of that type working normally.

Click **Restore** to re-enable it.

**Disable is refused while an enabled schedule still depends on the type.** A schedule that names no type uses the default `task` type, so disabling `task` is also refused while such a schedule is enabled. If you click **Disable** while a schedule still resolves to the type, the app shows an error and the type stays enabled. See [Scheduled tasks](https://codeherder.com/docs/scheduled-tasks/#how-schedules-behave) for how to find and update those schedules first, or use the CLI’s `--force` override below.

### Disabling an inherited type

Workspace owners and admins can disable an inherited type for this workspace and its descendants, without touching the parent workspace that owns it. On the inherited type’s detail page, click **Disable here**. This hides the type from new-task creation in this workspace and all its descendant workspaces. The parent workspace and any sibling workspaces are not affected. Existing tasks of that type keep working normally.

While a type is disabled, its detail page shows a **disabled** badge. Click **Restore** there to remove the per-workspace disable and make the type available here again.

Note that schema edits to an inherited type — its stages, fields, and parent rules — are still only possible in the workspace that owns it. See [Managing workspaces](https://codeherder.com/docs/workspaces/) for an overview of how groups and workspaces share resources down the hierarchy.

## Version history

Every time you save a type, CodeHerder records a snapshot of its configuration. The **Version history** panel on the type’s detail page lists every past version with its label, date, and any change note. Click **Restore** to roll back to a previous configuration. If the restore would remove a stage that still has live tasks, the app prompts you to migrate those tasks to a surviving stage first.

From the CLI, `ch workspace workflow versions <type>` lists the same history, newest first, paged with `--limit` and `--cursor`. Restoring a version is an app-only action for now. See [Version history and going back](https://codeherder.com/docs/version-history/) for how this compares with the other things CodeHerder keeps a history of.

## Built-in types: the shared definition and your own copy

A built-in type your workspace has not customised follows CodeHerder’s **shared definition**. It is built from your workspace’s stage library, so an edit to a shared stage reaches it automatically. It also picks up any improvement CodeHerder ships to the built-in itself.

Some workspaces, often older ones, hold their **own copy** of a built-in type instead. A copy is yours to edit freely, but it stops tracking the shared definition — a later change to the shared version, or to a shared stage it used to follow, no longer reaches it.

You can’t edit a built-in type directly until your workspace has its own copy. Trying to save one — from the editor or from `ch workspace workflow edit` — is refused, because there’s nothing local yet to save the edit into. To change a built-in type, submit a workflow proposal and have an owner or admin apply it. Applying the proposal is what gives your workspace its own copy, with your proposed change already applied. See [Proposing a workflow change for review](https://codeherder.com/docs/workflow-overrides/#proposing-a-workflow-change-for-review).

Disabling doesn’t need a copy first. **Disable** and **Restore** work on a built-in type exactly as described in *Disable and restore* above, whether or not your workspace has its own copy.

If your workspace later decides its own copy was a mistake, or just wants to catch up with the shared definition again, `adopt-shared` removes the copy and hands the type back to the shared definition — see *Go back to the shared definition* below.

## Manage workflows from the CLI

Workspace owners and admins can also manage the catalog from the `ch` CLI — useful for scripting changes or keeping a type’s schema in version control alongside your repo.

To view a workspace’s configured types, their pipelines, and field definitions:

```
ch workspace workflow show              # all types in the workspace
ch workspace workflow show --type bug   # one specific type
ch workspace workflow show --disabled   # only the types you've turned off
```

(`ch workspace task-schema` is a frozen alias — identical behaviour; you’ll still see it in older scripts.) See [Listing enabled and disabled records](https://codeherder.com/docs/using-the-cli/#listing-enabled-and-disabled-records) for how `--enabled` / `--disabled` work here. **Settings → Workflows** carries the same narrowing as an Enabled / Disabled / All picker above the list.

In the default view, inherited types are marked `[inherited]`; edit those from the workspace that owns them. Add `--json` to print each type’s full schema as machine-readable JSON — the exact shape `edit` expects.

### Create or replace a type

```
ch workspace workflow edit <type> --from-file <path>
```

(`set` is a frozen alias for `edit` — the two commands are identical; you’ll still see `set` in some older scripts and examples.)

`edit` **replaces the whole schema** for `<type>`: it creates the type if the key is new, or overwrites it entirely if a local copy already exists. Include every stage, gate, and field you want to keep — not just what you’re changing. If `<type>` is a built-in your workspace has not copied, `edit` is refused — see *Built-in types: the shared definition and your own copy* above for how to get your own copy first.

Add `--change-note <text>` to record an optional note against the version this edit mints — useful for saying why you made the change, alongside the automatic label, date, and stage-migration record. It shows up next to that version in *Version history* above.

The fastest way to edit an existing type is a show → edit → apply round-trip:

1. Run `ch workspace workflow show --json` and find the entry whose `typeKey` matches the type you want to change.
2. Copy that entry’s `schema` object into a file (for example, `bug.json`) and make your edits.
3. `ch workspace workflow edit bug --from-file bug.json`

The file you pass to `--from-file` can be either format: the type’s flattened schema object — the same one `show --json` returns under `schema` — or a composition document that builds the pipeline from your workspace’s shared stage library instead of spelling out every stage’s settings by hand. See [The stage library](https://codeherder.com/docs/stage-library/) for the composition format. Either way, a schema’s `key` field must match `<type>`, or be left out of the file entirely. Pass `-` instead of a path to read it from standard input.

If your workspace has set a composition policy — a floor of stages every library-built pipeline must include, an allow-list of model tiers it may use, or both — a composition document that violates it is refused, with every violation listed at once. The same policy also bounds what an [auto task](https://codeherder.com/docs/auto-tasks/) ’s agent may compose for itself. See that page for how to read and set the policy.

### Moving tasks off a stage you remove or rename

If `edit` would remove or rename a stage that still has tasks sitting in it, CodeHerder refuses the change and lists which stages are still occupied. Re-run the command with a `--migrate <old-stage>=<new-stage>` flag for each occupied stage to move those tasks onto a surviving stage before the change is applied — the CLI equivalent of the web editor’s prompt described in *Stages* above. You can repeat `--migrate` for more than one stage:

```
ch workspace workflow edit story --from-file story.json \
  --migrate drafting=ready
```

### Go back to the shared definition

If your workspace holds its own copy of a built-in type, `adopt-shared` removes that copy and hands the type back to CodeHerder’s shared definition — the type then tracks whatever the shared definition says, the same as a built-in type your workspace never copied.

Check what would happen first, without changing anything:

```
ch workspace workflow preview-adopt-shared feature
```

This prints a verdict for your workspace’s copy:

- **current** — your copy matches today’s shared definition already; adopting changes nothing.
- **stale** — your copy differs from the shared definition, but only in ways other workspaces on an older copy of this same built-in commonly share, usually because it hasn’t picked up a recent update yet.
- **customised** — your copy has a change of its own, or drops a stage the shared definition has.

The preview also lists which stages differ and, if adopting would remove a stage that still has tasks sitting in it, which stages you’d need to `--migrate` first — the same rule *Moving tasks off a stage you remove or rename* describes above.

Then adopt:

```
ch workspace workflow adopt-shared feature
```

A customised copy needs `--confirm-customised` to adopt, so you don’t lose a deliberate change by accident. Add `--migrate <old=new>` (repeatable) if the preview named an occupied stage the shared definition doesn’t have, and `--change-note <text>` to record why on the version this mints. To adopt every built-in type your workspace has a copy of in one pass, use `--all` in place of a type key — it reports each type’s own outcome (adopted, or why not) rather than stopping at the first refusal.

If your workspace disabled the type, it stays disabled after adopting.

**Before you adopt, save what you have.** Once your workspace’s copy is gone, its version history goes with it — `ch workspace workflow versions <type>` comes back empty for a type your workspace no longer copies, the same as for a built-in you never copied. If you might want your copy’s exact configuration again later, run `ch workspace workflow show --type <type> --json` and keep the output somewhere first.

`adopt-shared` needs a workspace owner or admin, run directly — not from inside an agent session.

For an overview across your workspace and everything nested under it — which built-in types each one tracks the shared definition for, and which hold their own copy and how it compares — see `ch workspace workflow-drift`.

### Disable and restore a type

```
ch workspace workflow disable <type> [--force]
ch workspace workflow enable <type>
```

`delete` is a documented alias for `disable` — the two commands are identical.

For a locally-owned type, `disable` is the same soft delete as clicking **Disable** in the web app (see *Disable and restore* above): the type is hidden from new-task creation, but any existing tasks of that type keep working normally. `enable` brings it back — the CLI equivalent of clicking **Restore** on the type’s detail page.

`disable` is refused while an enabled schedule still resolves to the type — the CLI lists every blocking schedule. A schedule that names no type uses the default `task` type, so it blocks disabling `task`. Add `--force` to disable the type anyway. Forcing it through doesn’t touch those schedules: each keeps firing on its own cadence, but every fire after that fails, and you’ll see the failure as the schedule’s last error until you update or disable the schedule itself. See [Scheduled tasks](https://codeherder.com/docs/scheduled-tasks/#how-schedules-behave) for more.

Disabling an inherited type scopes to this workspace and its descendants — the same as **Disable here** in the web app (see *Disabling an inherited type* above); the workspace that owns the type is unaffected. `enable` reverses it the same way **Restore** does.

## Related guides

- [Core concepts](https://codeherder.com/docs/concepts/) — the full object model this catalog builds on
- [Customising a task’s workflow](https://codeherder.com/docs/workflow-overrides/) — override one task’s pipeline, or propose a change to a built-in type for review
- [How work flows](https://codeherder.com/docs/how-work-flows/) — how a type’s stages and gates drive an agent through a task
- [The stage library](https://codeherder.com/docs/stage-library/) — the shared, reusable stages every built-in type is composed from
- [Auto tasks — the agent chooses the workflow](https://codeherder.com/docs/auto-tasks/) — a task whose agent composes its own pipeline, and the policy that bounds it
- [Understanding the task hierarchy](https://codeherder.com/docs/hierarchy/) — how allowed parent kinds shape nesting
- [Approvals & staying in control](https://codeherder.com/docs/approvals/) — how approval gates fit into a stage
- [Running a stage in your own container image](https://codeherder.com/docs/stage-images/) — two more stage-content settings, and where each lives depending on how the type is built
- [Which skills a stage gets](https://codeherder.com/docs/stage-skills/) — narrow a stage’s sessions to a specific list of skills instead of the workspace’s whole enabled set
- [Change shape](https://codeherder.com/docs/change-shape/) — the optional per-stage size limit, set from the CLI only
