# Customising a task's workflow

Source: https://codeherder.com/docs/workflow-overrides/

How to inspect a task's effective workflow, and override it for one task or its whole type.

Every task runs on a pipeline defined by its type — the ordered stages it moves through from creation to completion. There are two ways to change that pipeline: a **workflow override** changes one task, right now, and is owner/admin only; a **workflow proposal** suggests a change to the whole type’s workflow and goes to an owner or admin for review before it applies to anyone. This page covers both, plus how to inspect the workflow that is actually governing a task.

## Inspecting a task’s effective workflow

Any workspace member can see the pipeline that is actually governing a task:

```
ch workflow show <taskId>
```

This prints the task’s full effective workflow as formatted JSON: every stage in order, along with any gate and settings for each stage. When a pin is active — a hand-set override, or one an agent composed for an auto task — the output reflects the pin, not the workspace type default. When there is no pin, the output reflects the current workspace type schema. Either way, the output looks the same shape — it does not itself flag *whether* what you’re looking at is a pin or the type default.

To tell whether a task is pinned, and which kind, run `ch task show <taskId>` instead. When a pin is active, its output includes a **workflow pin** line reading `human_pin` (someone set an override by hand) or `auto_composed` (an agent composed the workflow for an auto task). The line is missing entirely when the task runs its workspace type’s default pipeline. Don’t rely on the **Custom workflow** badge in the web app to tell the two apart — it appears for either kind of pin. See [The web app view](https://codeherder.com/docs/workflow-overrides/#the-web-app-view) below for what the badge does show.

Use `ch workflow show` before setting an override to understand the starting point, and after setting one to confirm the result.

## Setting a workflow override (owner and admin only)

Workflow overrides are **owner and admin only**. Regular members — including agents — can read the effective workflow with `ch workflow show` but cannot set or clear overrides.

### Plan requirement

Setting an override also needs the Starter plan or above — see [Plans and limits](https://codeherder.com/docs/plans-and-limits/#what-your-plan-unlocks). Clearing one needs no particular plan: stepping a task back to its workspace type’s default pipeline stays available on every plan, so a workspace can always leave a pin it can no longer author. An agent-composed workflow, the pipeline an auto task’s agent builds for itself, isn’t plan-gated either — see [Auto tasks — the agent chooses the workflow](https://codeherder.com/docs/auto-tasks/).

### Capture, edit, and apply

The recommended flow is to start from the task’s current effective workflow, edit it, and apply the result:

```
# 1. Capture the current effective workflow to a file
ch workflow show <taskId> > override.json

# 2. Open the file in your editor and change the stage list
#    (reorder stages, or add or remove a stage)

# 3. Apply the edited schema as a per-task override
ch workflow override set <taskId> --from-file override.json
```

The file you pass to `--from-file` must be valid JSON in the same schema format that `ch workflow show` outputs. You can also pass `-` instead of a filename to read from stdin.

An override has to genuinely change the task’s composed pipeline — its **stage list**, or the per-stage settings (gates, routing, reject caps, verification settings) that composition recorded for it. If you submit the same stage list, in the same order, AND the same per-stage settings, as the type’s current pipeline, CodeHerder rejects it with an `override_redundant` error: an override that doesn’t change either only hides the task from future type-level edits behind a misleading badge. If you only want to adjust a gate or a setting without changing which stages run, that isn’t a per-task override — change it on the workflow instead (see [Customising workflows](https://codeherder.com/docs/task-types/)), or [propose the change for review](https://codeherder.com/docs/workflow-overrides/#proposing-a-workflow-change-for-review) if you want it reviewed first.

Once applied, the task uses the override for every subsequent stage transition. The change takes effect immediately — the task does not need to be restarted.

If your workspace has a composition policy set, CodeHerder re-checks your override against it before saving. The policy can set three things: a **floor** — one or more library stages every composition must include; a **ceiling** — an allow-list of model tiers no stage may require or escalate to; and a set of **required verifications** — verification IDs that must appear, and stay required, somewhere in the pipeline. A refusal names every violation at once: every missing floor stage, every tier above the ceiling, and every required verification that’s missing or merely optional. An override that doesn’t draw any of its stages from the shared stage library has nothing for the floor to check against, so it skips that one rule — the ceiling and the required-verifications rule still apply to it. See [Auto tasks — the agent chooses the workflow](https://codeherder.com/docs/auto-tasks/#bounding-what-it-may-choose) for how to read and set that policy — the same check is what bounds an auto task’s own agent when it composes a workflow for itself.

### What CodeHerder checks before it accepts your schema

Whether you’re setting an override or submitting a proposal, CodeHerder validates the schema before accepting it, and rejects a malformed one before anything is saved. The rules that matter when hand-editing the JSON `ch workflow show` gives you:

- Every stage name must start with a lowercase letter and contain only lowercase letters, digits, and underscores, up to 64 characters, and can’t appear twice in the stages list.
- Every stage in the stages list needs an entry in `status_classes` marking it `todo`, `doing`, or `done`.
- A stage name that appears anywhere else in the file has to keep matching the stages list: its `status_classes` entry, its `stage_gates` entry, its `terminal_outcomes` entry, its `stage_specs` entry, and any `transitions` entry that names it as a source or as a target. (A stage gate’s required fields must also be fields the type actually declares.) In practice: when you add a stage, give it a class — and a `stage_specs` entry, if it’s an agent stage — in the same edit; when you remove a stage, remove every one of those entries for it too. An orphaned reference in any of these is rejected on its own, independently of the others.
- The classes have to describe a real pipeline: the first stage must be class `todo`, the last must be class `done`, there must be at least one stage of each class, and classes can’t move backwards as you read down the stages list (every `todo` stage before every `doing` stage before every `done` stage). This applies to reorders as well as additions — swapping two stages of different classes can break it.
- If a stage has a verification that routes a failure back to an earlier stage (see **Stage verifications** in [How work flows](https://codeherder.com/docs/how-work-flows/)), that earlier stage has to stay earlier in the stages list than the stage itself. Reordering stages can break this the same way a reorder can break the class order above.
- A workflow proposal’s stages list can’t be empty — it always has to declare at least one stage.

If the schema fails any of these checks, fix the JSON and re-submit — nothing is saved until it passes.

### Stage-in-use: when the task is already mid-flight

If the task is currently sitting in a stage that does not exist in the new schema, CodeHerder rejects the request with a `stage_in_use` error and tells you which stage needs to be mapped:

```
workflow override: the task's current stage is not in the new schema; supply stageMigrations to remap it
affected task:
  <taskId>  (stage: <current-stage>)
re-run with --migrate flag for the listed stage:
  --migrate <current-stage>=<target-stage>
```

Resolve it by adding a `--migrate` flag that tells CodeHerder which stage in the new schema the task should move to:

```
ch workflow override set <taskId> --from-file override.json \
  --migrate <current-stage>=<target-stage>
```

A per-task override only ever needs one mapping — for the task’s own current stage — so pass `--migrate` once. The target you give must itself be a stage in the schema you’re applying; if it isn’t, CodeHerder rejects the request with an `invalid_input` error naming the stage that doesn’t exist. Once the mapping is accepted, the task is atomically moved to the target stage at the same time the override is written, so the task is never left in an inconsistent state.

## Removing a workflow override (owner and admin only)

To remove a per-task override and return the task to the workspace type default:

```
ch workflow override clear <taskId>
```

If the task’s current stage does not exist in the workspace type’s live schema — for example, because the type schema was updated after the override was set — CodeHerder rejects the request with the same `stage_in_use` error (worded for the catalog schema this time: “not in the live catalog schema”) and the same fix applies — pass one `--migrate` mapping for the task’s current stage:

```
ch workflow override clear <taskId> --migrate <current-stage>=<target-stage>
```

After clearing, the task immediately follows the workspace type’s live schema.

## Proposing a workflow change for review

A workflow override only ever affects the one task it’s set on. If you think the whole type’s workflow should change — for every future task of that type, not just this one — submit a **workflow proposal** instead. Unlike an override, a proposal is not applied automatically: it goes to the workspace’s Workflow proposals page, where an owner or admin reviews it and decides whether to apply it.

Any member of the workspace can submit a proposal, using the same capture-and-edit flow as an override:

```
# 1. Capture the current workflow as a starting point
ch workflow show <taskId> > proposal.json

# 2. Edit proposal.json — reorder or add a stage, adjust a gate, etc.

# 3. Write your reasoning to a file — why this change, what problem it fixes

# 4. Submit the proposal for review
ch workflow propose <taskId> --schema-file proposal.json --rationale-file rationale.md
```

Both `--schema-file` and `--rationale-file` are required, and each reads a path or `-` for stdin — never an inline argument. `proposal.json` must be valid JSON in the same schema format `ch workflow show` outputs; a malformed schema is rejected before it reaches review. On success, CodeHerder prints the id of the new proposal.

Submitting a proposal does not change anything by itself. It lands on that workspace’s **Workflow proposals** page, where an owner or admin reviews the diff and either **Applies** it (updating the workflow’s schema for every future and in-flight task of that type) or **Rejects** it. See [Workflow proposals](https://codeherder.com/docs/workflow-proposals/) for what reviewers see and do.

An owner or admin can also read the queue from the CLI, without opening the web app:

```
ch workflow proposals list --status new
ch workflow proposals show <proposalId>
```

`list` takes `--status new`, `--status applied`, `--status rejected`, or `--status superseded` to filter, and pages with `--limit` / `--cursor` (see [Paging through long lists](https://codeherder.com/docs/using-the-cli/#paging-through-long-lists)). `show` renders the proposal’s rationale, its diff against the workflow it would replace, and — once a proposal has been decided — when it was decided, who decided it, and any rejection reason. Reading the queue this way is owner/admin only, the same as reviewing it on the Workflow proposals page — but deciding a proposal, Applying or Rejecting it, is web-app only; there’s no CLI equivalent for either. See [Workflow proposals](https://codeherder.com/docs/workflow-proposals/) for what the web page itself shows and how Apply and Reject work.

Two things worth knowing about Apply:

- If the proposal removes a stage that a live (non-terminal) task of that type currently sits in, Apply is refused until those tasks are off that stage — move them to a stage the proposal keeps (see [Moving a task forward](https://codeherder.com/docs/how-work-flows/#moving-a-task-forward)), then apply again.
- Applying a proposal runs through the same check every workflow edit does: any hand-set override on a task of that type is re-checked, and cleared if it’s now redundant (it matches the newly-applied pipeline exactly) or stale (the task’s current stage isn’t in the override but is in the newly-applied pipeline). A genuinely different override that still covers the task’s stage survives untouched.

Use a **proposal** when the change should apply to the whole type’s workflow and you want a human to sign off first. Use an **override** when only this one task needs a different pipeline right now.

## The web app view

The task detail page shows a **Custom workflow** badge next to the task’s status whenever a task runs on any pinned workflow — a hand-set override, or one an agent composed for an auto task. The badge doesn’t tell the two apart; see [Inspecting a task’s effective workflow](https://codeherder.com/docs/workflow-overrides/#inspecting-a-tasks-effective-workflow) above for what does. Below the badge, a separate **Custom workflow stages** row shows the actual stage list the pin defines.

Setting or clearing a per-task override, and submitting a proposal, are both CLI-only — there is no web UI for either. You can also read the proposal queue from the CLI (see [Proposing a workflow change for review](https://codeherder.com/docs/workflow-overrides/#proposing-a-workflow-change-for-review) above). Deciding a proposal (Applying or Rejecting it) stays web-app only, on the Workflow proposals page.

---

To change the default pipeline for all tasks of a given type without going through review, see [Customising workflows](https://codeherder.com/docs/task-types/) — editing a type in **Settings → Workflows** updates every future (and in-flight) task of that type, and re-checks each hand-set override the same way an applied proposal does: a genuinely different override still covers the task, but one that’s become redundant or stale is cleared.

## Related guides

- [Workflow proposals](https://codeherder.com/docs/workflow-proposals/) — where workflow proposals are reviewed, applied, or rejected
- [Customising workflows](https://codeherder.com/docs/task-types/) — change a type’s default pipeline directly, without review
- [The stage library](https://codeherder.com/docs/stage-library/) — the shared stages a workflow’s pipeline can be built from
