# Using the ch CLI

Source: https://codeherder.com/docs/using-the-cli/

The conventions every ch command follows, so the CLI reads the same everywhere.

The `ch` CLI is workspace-scoped, and every command follows the same pattern. Once you’ve used a couple of verbs, you already know the shape of the rest. This page covers the rules that hold across the whole CLI, so you learn them once.

## Command structure

Every `ch` invocation follows the same pattern:

```
ch [global flags] <verb> <subcommand> [args]
```

A **verb** is a noun like `task`, `device`, or `webhook`; a **subcommand** is the action on it, like `list`, `show`, or `create`. Global flags, covered below, can go anywhere on the line.

## Finding your way around

You don’t need a reference open in another tab. Help is built into three levels:

- `ch --help` always prints every verb at a glance.
- `ch <verb> --help` (or the bare verb, e.g. `ch task`) prints that verb’s subcommands plus a worked example.
- `ch <verb> <subcommand> --help` prints the detail for that one subcommand: its arguments, flags, and defaults.

Bare `ch`, with no verb at all, is different. In a real terminal on macOS or Linux it opens an interactive session console. See [Your sessions in the terminal](https://codeherder.com/docs/terminal-console/). It prints the same list as `ch --help` only when it can’t open the console. That happens when output is piped or redirected, when `--json` is set, or on a host other than macOS or Linux.

If you mistype a subcommand, the CLI tells you it doesn’t recognize it.

## Short-form aliases

Three subcommands are common enough to get a short form. The short form works on **every** verb that has that subcommand:

| Full form | Short form | Example |
| --- | --- | --- |
| `list` | `ls` | `ch task ls` |
| `show` | `get` | `ch device get <id>` |
| `delete` | `rm` | `ch webhook rm <id>` |

A short form works only where the full subcommand exists. A repo, for example, has no `delete`. You retire it with `archive` / `restore` instead, so `ch repo rm` isn’t recognized either.

A few verbs add their own extra alias on top of a specific subcommand: `ch repo restore` is also `ch repo unarchive`, and `ch webhook event-types` is also `ch webhook catalog`. These belong to that one verb only. They aren’t part of the `ls` / `get` / `rm` set.

A few verbs spell the write op differently, and `edit`, `set` and `update` all name that same op. A verb accepts whichever one it defines, and the other two run it. So `ch variable edit` is `ch variable set`, and `ch task update` is `ch task edit`. Use the spelling in the verb’s `--help`.

## Changing a record’s own fields: `edit`

To change a record’s own fields, you always use `ch <thing> edit`. It works on `task`, `workspace`, `schedule`, `webhook`, `inbound-endpoint`, `repo`, `agent`, `team`, `device`, `member`, `stage`, `survey`, and `skill`. Run `ch <thing> edit --help` to see which fields a record accepts. The fields differ from one kind of record to the next, but the verb stays the same. `ch stage edit` and `ch skill edit` are the exceptions. `ch stage edit` takes one JSON document with only the parts you change. The stage keeps every part you leave out. `ch skill edit <slug> --from-dir <dir>` replaces the skill’s whole file set with the folder’s content. Neither takes one flag per field, so their `--help` doesn’t list separate fields.

## Removing a record: `delete` is permanent, `archive` / `restore` is reversible

`ch <thing> delete` (short form `rm`, see [Short-form aliases](https://codeherder.com/docs/using-the-cli/#short-form-aliases) above) means an irreversible removal. It works on `workspace`, `schedule`, `webhook`, `inbound-endpoint`, and `skill` (see [Skills](https://codeherder.com/docs/skills/)). Run `ch <thing> delete --help` for the specifics, or see that entity’s own guide.

For `skill`, `schedule`, `webhook`, and `inbound-endpoint`, you can’t undo a delete. `ch workspace delete` is permanent too. It runs only on a workspace you’ve already archived, so archiving is the confirmation step.

**`ch device delete` and `ch agent delete` are retired.** They used to archive the record rather than remove it, so `delete` meant different things on different records. Use `ch device archive` or `ch agent archive` instead. If you type the retired word, the CLI prints `Did you mean: ch device archive ?` (or `ch agent archive ?`) instead of failing silently. The full list of reversible commands: `ch device archive` (and `restore`), `ch agent archive` (and `restore`), `ch repo archive` (and `restore`, alias `unarchive`), `ch workspace archive` (and `restore`, the reversible step before the permanent delete above), and `ch wiki archive` (and `restore`). `ch member keys delete <hash>` reads similarly but only revokes one API key; it doesn’t remove the member. See [Managing a record’s child collections](https://codeherder.com/docs/using-the-cli/#managing-a-records-child-collections) below.

Three more verbs answer to `delete` / `rm` without removing anything permanently. Each one is another name for a different command. `ch msg delete` is the same command as `ch msg archive`: it hides a message you sent, and `ch msg restore` brings it back. `ch variable delete` is the same command as `ch variable unset`: it clears that scope’s own row for a key, nothing more. `ch stage delete` is the same command as `ch stage disable`. You can undo it with `ch stage enable`. The CLI refuses it while a workflow still uses that stage. Typing `rm` on any of the three runs the reversible command, so check which one you’re running before you rely on the short form.

## Managing a record’s child collections

Some records own a whole collection of child records. Examples are a team’s members, a member’s API keys, and a device’s workspace links. A task owns its comments, watchers, blockers, dependencies, fields, verifications, attachments, repos, and merge refs. To reach one of these, use `ch <parent> <child> <subcommand>`. The subcommands are the same `list` / `create` / `edit` / `delete` as everywhere else, one level deeper:

```
ch team members create <team> <member> --role admin
```

The tables below show the ones you’re most likely to use. They don’t list every collection. A workspace’s quality-review queues and workflow proposals work differently, and their own pages cover them. Seven belong to a record other than a task:

| Command | What it holds | Bare ref lists? |
| --- | --- | --- |
| `ch team members` | A team’s membership (see [Teams](https://codeherder.com/docs/members-and-teams/#teams)) | Yes |
| `ch member keys` | A member’s API keys (see [Minting API keys](https://codeherder.com/docs/members-and-teams/#minting-api-keys)) | Yes |
| `ch device workspaces` | The workspaces a device is linked to (see [Sharing a device across workspaces](https://codeherder.com/docs/devices/#sharing-a-device-across-workspaces) for more) | Yes |
| `ch device secrets` | Which keyring keys on a device its owner has approved a session to use (see [Secrets on a device](https://codeherder.com/docs/device-secrets/#step-3--get-the-reference-acknowledged)) | Yes |
| `ch agent config` | An agent’s launch configs (see [Agents and the CLI](https://codeherder.com/docs/agents-and-cli/)) | No, a subcommand is always required |
| `ch inbound-endpoint rules` | An inbound webhook’s routing rules (see [Inbound webhooks](https://codeherder.com/docs/inbound-webhooks/)) | No, a subcommand is always required |
| `ch workspace workflow` | A workspace’s workflow schemas (see [Customising workflows](https://codeherder.com/docs/task-types/)) | No, a subcommand is always required |

Nine more all belong to a task:

| Command | What it holds | Bare ref lists? |
| --- | --- | --- |
| `ch task comments` | The task’s comment thread (see [Collaborating](https://codeherder.com/docs/collaborating/#task-comments-and-hand-off-notes)) | Yes, and inside an agent session you can drop the task too |
| `ch task watchers` | Who’s subscribed to the task (see [Watching tasks and notifications](https://codeherder.com/docs/watching/#seeing-who-watches-a-task)) | Yes, and inside an agent session you can drop the task too |
| `ch task blockers` | The task’s blockers (see [Blockers and blocked tasks](https://codeherder.com/docs/blockers/#viewing-a-tasks-blockers)) | Yes, and inside an agent session you can drop the task too |
| `ch task deps` | The task’s dependency graph (see [Task dependencies](https://codeherder.com/docs/collaborating/#task-dependencies) for more) | Yes, and inside an agent session you can drop the task too |
| `ch task fields` | The task’s declared field values (see [Editing and cancelling tasks](https://codeherder.com/docs/editing-tasks/#editing-per-task-fields-acceptance-criteria-repro-steps-and-others)) | Yes, and inside an agent session you can drop the task too |
| `ch task verifications` | The task’s recorded verification results (see [How work flows](https://codeherder.com/docs/how-work-flows/)) | Yes, and inside an agent session you can drop the task too |
| `ch task attachments` | The task’s attachments (see [Attaching files and images](https://codeherder.com/docs/attachments/#managing-attachments-from-the-cli)) | Yes, and inside an agent session you can drop the task too |
| `ch task repos` | The repos a task works in (see [Seeing which repos a task works in](https://codeherder.com/docs/multi-repo-tasks/#seeing-which-repos-a-task-works-in)) | Yes, and inside an agent session you can drop the task too |
| `ch task merge-refs` | The task’s recorded merge requests, one per repo (see [How the work lands](https://codeherder.com/docs/multi-repo-tasks/#how-the-work-lands)) | Yes, and inside an agent session you can drop the task too |

Not every collection has every subcommand:

- `ch member keys` has no `edit`.
- `ch workspace workflow` reads with `show`, not `list`. It has no `create`. Instead, `edit` creates the type the first time and updates it after. Its `delete` disables the type, and you can undo that.
- `ch device secrets` has only `list` and `set`. `set` replaces the device’s whole list in one call. It doesn’t add one key. So pass every key you still want, not just the new one.

A task’s own nine differ too:

- `ch task blockers` uses `resolve` instead of `delete`. A blocker is closed out, not removed.
- `ch task deps` adds `dependents`, which lists the tasks waiting on this one.
- `ch task fields` has `list`, `show`, and `versions`. `versions <key>` lists a field’s earlier values, newest first, one page at a time. It’s read-only.
- `ch task repos` has `list`, `create`, and `delete`. `create` attaches a repo to the task and `delete` removes it. Deleting a repo that isn’t attached, or the task’s last repo, succeeds.
- `ch task merge-refs` has the plain `list` / `create` / `delete`.

To change a field’s value, use `ch task edit <taskId> --<key> "text"` (or `--<key>-file <path>`). Run `ch task fields <taskId>` to see the field keys the task accepts. `ch task fields show --value` still writes a field too, but `ch task edit` is the command to use. See [Editing and cancelling tasks](https://codeherder.com/docs/editing-tasks/#editing-per-task-fields-acceptance-criteria-repro-steps-and-others).

Run `ch <parent> <child> --help` to see which subcommands a collection supports.

Most of these also let you drop the subcommand and just give the parent’s ref. That lists the collection. It works on four of the seven non-task rows above (all but `agent config`, `inbound-endpoint rules`, and `workspace workflow`), and on all nine task rows:

```
ch team members <team>
# same as: ch team members list <team>
```

The nine task rows go one step further. Inside an agent session the task ref is optional (see [Agents and the CLI](https://codeherder.com/docs/agents-and-cli/)). So if you drop the ref too, the command lists the current task’s collection:

```
ch task comments
# same as: ch task comments list <the task CH_TASK_ID points to>
```

The other three (`agent config`, `inbound-endpoint rules`, `workspace workflow`) always need a subcommand. If you leave it off, you get a usage error.

Some older command names came before this pattern. They still work, so existing scripts keep running:

- On `ch task`: `comment` is `create` on `ch task comments`; `watch` / `unwatch` are `create` / `delete` on `ch task watchers`; `block` / `unblock` are `create` / `resolve` on `ch task blockers`; `depends-on` / `undepend` are `create` / `delete` on `ch task deps`; `field` is `show` on `ch task fields`; `verify` is `create` on `ch task verifications`; `attach` / `attachment` are `create` / `show` on `ch task attachments`; and `set-merge-ref` / `clear-merge-ref` are `create` / `delete` on `ch task merge-refs`.

Use whichever reads better to you.

## JSON output and scripting

Add `--json` to any command to get the raw JSON response instead of the formatted table or summary. This helps when you pipe into `jq` or another script:

```
ch task list --status code --json | jq '.data[].id'
```

`--json` is a global flag. It works the same way on every verb.

Exit codes are consistent, so a script can branch on success or failure:

- A usage error (bad flags, missing required argument) exits `2`.
- `ch task validate <taskId>` exits `0` when the task is ready to advance and `2` when it fails a gate check. See [Writing tasks an agent can build](https://codeherder.com/docs/writing-tasks/) for what the gate checks.
- `ch githost auth-check` exits `1` when the git-host CLI for the current repo (`gh` or `glab`) isn’t authenticated.

## Paging through long lists

Most commands that list many records return one page at a time. This includes tasks, comments, messages, blockers, attachments, sandboxes, and activity. Two flags control paging, the same way on every command:

- `--limit N` sets how many rows come back in this page.
- `--cursor <token>` resumes from where an earlier page left off.

Each command has its own default and ceiling:

| Command | Default rows | Max rows |
| --- | --- | --- |
| `ch task list` | 50 | 200 |
| `ch task list --watching` | 50 | 200 |
| `ch task list --awaiting-approval` | 200 | 500 |
| `ch task children` | 50 | 200 |
| `ch task comments` | 50 | 200 |
| `ch msg inbox` | 50 | 200 |
| `ch sandbox list` (task scope) | 50 | 200 |
| `ch sandbox list workspace` | 200 | 500 |
| `ch session list` | 50 | 200 |
| `ch task blockers` | 200 | 500 |
| `ch task attachments` | 200 | 500 |
| `ch activity` (all four scopes) | 50 | 200 |

Leave `--limit` off for the default; raise it toward the max to see more per call.

### How you know there’s more

Table output prints one line to standard error whenever another page is waiting:

```
more results — re-run with --cursor <token>
```

The line goes to stderr on purpose. So when you pipe or redirect stdout, the line stays out of your data.

With `--json`, the same signal is a `next_cursor` key. It’s present only when there’s another page, and it’s never present but empty. So a script stops when the key is missing, not when the value is empty. Here are two calls, worked by hand:

```
ch task list --limit 20 --json | jq '.next_cursor'
# a token: more rows exist, so feed it back in
ch task list --limit 20 --cursor <that token> --json | jq '.next_cursor'
# null: the key is absent, so you're at the end
```

### A short page doesn’t mean you’re done

Stop when a page comes back with no cursor, not when it looks short. A few lists, such as `ch task list --watching`, can return fewer rows than you asked for and still give a cursor. This happens because they filter rows after they set the page boundary. Only a missing cursor tells you that you’ve seen everything.

### The one command that pages for you

`ch task tree` takes no `--cursor` and needs none. It fetches every level itself, so a task with many children still shows in full. Its only size flag is `--depth` (default 5). That limits how deep it prints, not how much it fetches.

`ch activity` also takes `--cursor`, but it moves forward through history and stops in a different way. See [Activity feeds](https://codeherder.com/docs/activity/).

## Choosing whose data a list shows

Four commands read data that can belong to more than one thing: `ch activity`, `ch costs`, `ch session list`, and `ch sandbox list`. All four take the same kind of leading keyword to say whose data you want:

| Command | Accepted scopes | Default with no keyword |
| --- | --- | --- |
| `ch activity` | `me`, `agent <ref>`, `workspace [<ref>]`, `task [<ref>]` | `me` |
| `ch costs` | `me`, `agent <ref>`, `task [<ref>]`, `session [<ref>]`, `workspace [<ref>]` | `me` |
| `ch session list` | `me`, `task [<ref>]`, `agent <ref>`, `device <ref>`, `workspace [<ref>]` | `task` |
| `ch sandbox list` | `task [<ref>]`, `workspace [<ref>]` | `task` |

The keyword follows the same rules on all four commands:

- It comes first, before any flags. Write `ch session list agent Builder --all`, not `ch session list --all agent Builder`.
- `workspace` can also be spelled `ws`.
- `agent` and `device` need a ref right after the keyword: a display name or a full id. If you leave it off, the command points you to `ch agent list` or `ch device list` to find one.
- `workspace`, `task`, and `session` refs are optional: `workspace` falls back to `--workspace` / `CH_WORKSPACE_ID`, `task` to `CH_TASK_ID`, `session` to `CH_SESSION_ID`.
- You can’t add a second scope keyword after `agent`, `device`, `workspace`, or `task`. The CLI doesn’t guess which one you meant. It gives a usage error that names both scopes.
- `ch session list` and `ch sandbox list` also accept a bare task id with no `task` keyword. The same error applies when you put a task id next to one of those four keywords.
- After `me` there’s nothing to combine. An extra keyword there fails as an unrecognized argument.

```
ch activity workspace                 # this whole workspace (or pass a ref)
ch costs agent Builder                # one agent's spend, by display name
ch session list device build-box-01   # everything that's run on one device
ch sandbox list ws                    # every sandbox in the workspace ("ws" for "workspace")
```

Each command’s own flags, such as filters by type, time window, or state, are on its own page: [Activity feeds](https://codeherder.com/docs/activity/), [Understanding costs](https://codeherder.com/docs/costs/), [Sessions from the command line](https://codeherder.com/docs/session-cli/), and [Agents and the CLI](https://codeherder.com/docs/agents-and-cli/).

## Listing archived records

Several commands use the same flags to decide whether a list includes archived rows: `ch device list`, `ch repo list`, `ch workspace list`, `ch agent list`, `ch wiki list`, and `ch msg inbox`.

- No flag: archived rows are hidden. This is the default on all of these commands.
- `--active`: the same default, spelled out.
- `--all`: every row, active and archived together.
- `--archived`: archived rows only.

You can use only one of the three flags. If you pass two, the command fails with a message that names all three:

```
$ ch device list --all --archived
device list: --active, --all and --archived are mutually exclusive
```

`ch wiki list` follows the same rule, with one difference. There, only an admin or owner can use `--all` and `--archived`. See [Workspace wiki](https://codeherder.com/docs/memory/#governance) for why. `ch wiki folders` shows the wiki’s folder tree, not a list of pages. It takes the same three flags, with the same limit.

No other list command takes `--archived`. `ch session list`, `ch sandbox list`, and `ch task blockers` also accept `--active` / `--all`. On these commands the flags mean open or closed, not archived. Open means a session or sandbox that still runs, or a blocker that isn’t resolved.

A task’s own `archived` status is a separate thing. See [Archiving a finished task](https://codeherder.com/docs/editing-tasks/#archiving-a-finished-task) for that.

## Listing enabled and disabled records

Several commands use a second pair of flags to decide whether a list includes disabled rows: `ch device list`, `ch agent list`, `ch schedule list`, `ch skill list`, `ch stage list`, `ch survey list`, `ch observability alerts list`, `ch session subscriptions list`, `ch task subscriptions list`, and `ch workspace workflow show`.

- No flag: every row comes back, enabled and disabled together. This is the default on all of these commands.
- `--enabled`: enabled rows only.
- `--disabled`: disabled rows only.

You can use only one of the two flags. If you pass both, the command fails with a message that names both:

```
$ ch schedule list --enabled --disabled
schedule list: --enabled and --disabled are mutually exclusive
```

A narrowed call looks like this:

```
ch schedule list --enabled --limit 10
```

`ch workspace workflow show` reads with `show`, not `list` (see [Managing a record’s child collections](https://codeherder.com/docs/using-the-cli/#managing-a-records-child-collections) above). It still takes the same pair. `ch workspace workflow show --disabled` lists only the types you’ve turned off.

These flags only filter a list. To turn a record on or off, use commands such as `ch schedule enable` / `disable`, `ch skill enable` / `disable`, and `ch stage enable` / `disable`. Each record’s own guide covers those.

## Supplying text: inline, a file, or stdin

Some flags take a body of text, such as task titles and descriptions, comments, wiki pages, and message bodies. These flags accept text in three ways:

- **Inline**: `--flag "some text"` (or `--flag=text`).
- **A file**: `--flag-file <path>` reads the value from a file. Use this one first.
- **Stdin**: `--flag-file -` (or, on some flags, a bare `--flag -`) reads from standard input. Only one flag per command can read from stdin. If a command takes more than one text flag, pipe stdin into one and give the rest inline or from a file.

Most of these flags also accept the older `--flag --from-file <path>` form. A command with only one text flag also accepts a standalone `--from-file <path>`, because it can mean only that flag. You’ll see both forms in older scripts. A command with more than one text flag has no standalone `--from-file`, because it wouldn’t be clear which flag it’s for. Examples are `ch task create` (`--title` and `--description`) and `ch agent config edit` (`--persona` and `--system-prompt`).

Use the file and stdin forms for anything long or multi-line, such as a comment body, a spec, or acceptance criteria. That way your shell’s quoting doesn’t get in the way. See [Writing tasks an agent can build](https://codeherder.com/docs/writing-tasks/) for a worked example using `--title` and `--description`.

## Referring to things on the command line

Every `ch` verb needs a way to point at a specific record, such as a task, a device, or an agent. Most kinds of record follow one rule. The exceptions are workspaces, variables and secrets, `--created-by`, and one flag that takes only a secret’s id.

**A full 36-character UUID always works**, for any kind of record, and needs no workspace. For some kinds of record it’s the *only* thing that works. The CLI rejects a shortened prefix:

```
$ ch task show 019e4d24
task show: "019e4d24" is not a full 36-char UUID — run `ch task list` to find the full id
```

There’s a reason for this. Ids follow time order, so records made close together share a long prefix. A prefix match could point at the wrong record. Run the verb’s `list` (or `ls`) subcommand and copy the full value from the `ID` column instead. A **task, webhook, sandbox, session, blocker, launch config, attachment, or message** has no human-readable name, so this UUID-only rule applies to all of those.

**For everything else, a name works too. For a wiki page, its slug works.** Agents, members, devices, teams, repos, and schedules all have a human-readable name, and a wiki page has a slug. `ch` searches your current workspace and ignores case. It tries an **exact** match, then a **prefix** match, then a **substring** match. It stops at the first step that finds anything:

```
ch device show macbook           # substring match on the device's name (or hostname, if unnamed)
ch schedule show "Simplify app"  # prefix match on the title
```

| You’re referring to… | The name it matches |
| --- | --- |
| An agent | Display name |
| A member | Display name |
| A device | Name: the one you set with `ch device edit --name`, or its hostname if you haven’t set one (see [Naming a device](https://codeherder.com/docs/devices/#naming-a-device)) |
| A team | Name |
| A repo | Name |
| A schedule | Title |
| A wiki page | Slug |

An archived wiki page matches only for an admin or owner. For everyone else, a slug lookup searches live pages only. See [Workspace wiki](https://codeherder.com/docs/memory/#governance) for how to find and restore an archived page.

If more than one record matches at the same step, `ch` doesn’t guess. It fails and lists every match, so you can be more specific:

```
$ ch device show build-box-
device show: ambiguous device ref "build-box-": 2 matches — be more specific
  019f2000-0000-7000-8000-0000000000b1  build-box-01
  019f2000-0000-7000-8000-0000000000b2  build-box-02
```

Matching a name needs a workspace to search within. Without one, `ch` tells you so instead of guessing:

```
$ ch device show macbook
device show: "macbook" is not a full 36-char UUID and no workspace is set to resolve a name by — set --workspace/CH_WORKSPACE_ID, or pass the full id from `ch device list`
```

Repos follow this same name-or-UUID rule, with one difference. A repo’s full UUID works even with no workspace set.

Variables and secrets are different. You name one by its **exact key**, such as `DEPLOY_TOKEN`, plus an optional scope (`device <ref>` or `agent <ref>`). There is no prefix or substring match, and no id lookup. Every `ch variable` command needs a workspace. See [Variables](https://codeherder.com/docs/variables/#setting-one-from-the-cli).

One flag is the exception: `--credential-ref ENV=<secretId>` on `ch agent config edit` / `create` always takes the secret’s id, never its name. See [Secrets](https://codeherder.com/docs/secrets/) for how to give an agent a secret.

**Workspaces take a name, slug, slug path, or id.** The global `--workspace` flag, `CH_WORKSPACE_ID`, and most workspace arguments accept any of these four forms. Examples are `ch workspace show <ref>`, `ch workspace edit <ref>`, and both arguments of `ch workspace set-parent <child> <parent>`. The one exception is the `--parent` flag on `ch workspace create`. It takes the new parent’s full id, with no name lookup.

```
ch task ls --workspace acme/platform
```

- Matching uses the same exact → prefix → substring steps as above. At each step, `ch` checks the slug path, then the slug, then the name. It moves to the next step only when all three find nothing. So a partial workspace name works too, like a partial device or schedule name. `ch task list --workspace form` finds `platform` by substring.
- `ch` searches every workspace you can reach, however many there are.
- A workspace still matches by its old slug path after you move it or its parent. `ch` also prints a note to stderr with the new path. So a script that uses the old path keeps working, and you see that the workspace moved: `note: workspace ref "acme/old" has moved — canonical path is now "acme/new"`
- An archived workspace matches by ref just like an active one.
- If more than one workspace matches at the step that decided the search, `ch` doesn’t guess: `$ ch task list --workspace acme task list: ambiguous workspace ref "acme" matches multiple workspaces: acme/platform (019f2000-0000-7000-8000-0000000000b1), acme/payments (019f2000-0000-7000-8000-0000000000b2)`
- No match at all looks like this: `$ ch task list --workspace nonesuch task list: no workspace matching "nonesuch" (try `ch workspace list` for names, slugs, paths and ids)`
- `ch workspace list` prints the `ID`, `NAME`, and `PATH` you need. A **slug path** is the workspace’s parent slugs and its own slug, joined with `/`. It’s also the part of the web app’s address right before `/-/` (in `{{app_base}}/acme/platform/-/tasks/<id>`, the slug path is `acme/platform`).
- `CH_WORKSPACE_ID` accepts all four forms too. Despite the name, it doesn’t have to be a UUID.

For example, `ch workspace show acme/platform` prints that workspace’s id, name, status, parent, and root.

**`ch task list --created-by`** accepts a full UUID, or `me` for yourself. It rejects a shortened id, the same way the UUID-only kinds above do.

| You’re referring to… | Accepted forms |
| --- | --- |
| An agent, member, device, team, repo, or schedule | Full UUID, or its name (exact → prefix → substring) |
| A wiki page | Full UUID, or its slug (exact → prefix → substring; live pages only, unless you’re an admin or owner) |
| A variable or secret | Its exact key at one scope (no prefix, substring, or id match) |
| A task, webhook, sandbox, session, blocker, launch config, attachment, or message | Full UUID only |
| A workspace | Name, slug, slug path, or UUID. Any workspace you can reach, including archived or moved ones (`--parent` on `ch workspace create` needs the id) |
| `ch task list --created-by` | Full UUID, or `me` |

Two more kinds of reference have their own rules, on their own pages. For the task a command uses by default from `CH_TASK_ID`, see [Agents and the CLI](https://codeherder.com/docs/agents-and-cli/). For who a message goes to, see [Messages and your inbox](https://codeherder.com/docs/messages/).

## Identity, workspace, and where they come from

Every command needs to know who you are and which workspace to work in. By default both come from your environment (`CH_TOKEN`, `CH_WORKSPACE_ID`). They can also come from a named profile or from a repo’s committed [project defaults file](https://codeherder.com/docs/project-config/). You can override them on one command with `--token` / `--workspace`. See [Credentials and profiles](https://codeherder.com/docs/credentials/) for the full picture, including which source wins when more than one is set.

In the same way, task commands take their task from `CH_TASK_ID` by default. See [Agents and the CLI](https://codeherder.com/docs/agents-and-cli/) for how that works.

One more global flag helps in scripts. `--no-retry` (or `CH_NO_RETRY=1`) turns off the automatic retry after a short network error. The CLI retries only commands that are safe to repeat. Leave retries on when you work by hand. Turn them off when you want a script to fail fast on the first network error.

`--no-auto-update` skips the self-update check that `ch start` and `ch session attach` otherwise run first. See [Updating the CLI](https://codeherder.com/docs/updating/) for how that check behaves and how to opt a non-interactive caller into it.

## Related guides

- [Agents and the CLI](https://codeherder.com/docs/agents-and-cli/) — installing the CLI, agent configuration, and the current-task default
- [Credentials and profiles](https://codeherder.com/docs/credentials/) — signing in, API keys, profiles, and credential precedence
- [Quickstart](https://codeherder.com/docs/quickstart/) — install the CLI and ship your first task
- [Writing tasks an agent can build](https://codeherder.com/docs/writing-tasks/) — the three ways to supply text, in action
