# Reading the task flow report

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

See where a task's elapsed time goes between arrival and completion, and why ready work isn't running.

[Understanding costs](https://codeherder.com/docs/costs/) answers “what did this cost?” [Observability](https://codeherder.com/docs/tool-time/) answers “where did the time go inside a session?” Task flow answers a third question: where does a task’s elapsed time go between arrival and completion, and why isn’t ready work running?

## Where to find it

Today you read the workspace-wide report **from the command line**. The commands below — three `ch observability flow` subcommands, plus `ch task flow` for one task’s own swimlane — print every figure this page describes, for a workspace or a group.

One piece of it is in the app already: a task’s own page carries a **Lifecycle flow** panel, covered in [One task’s own flow](https://codeherder.com/docs/task-flow/#one-tasks-own-flow).

The workspace report also has a page in the app, with the panels described below. Nothing links to it yet, so don’t go hunting for it in the sidebar or on the Observability page — use the commands instead. The panel descriptions still tell you what each figure means, because the command line prints the same ones.

Like the tool-time report, task flow covers a workspace, or a group — a group’s report covers every workspace nested beneath it.

## The summary

Five figures cover the window. `ch observability flow` prints them, and they head the report’s page as a row of stat tiles:

- **Arrivals** — how many tasks were created in the window.
- **Completions** — how many tasks finished in the window.
- **WIP** — work in progress: how many of this window’s tasks are still open. It asks whether the task is open now, not whether it was open when the window closed.
- **Completion latency** — how long a task that finished in the window took from arrival to finishing, p50 with p95 alongside it.
- **Queue age** — how long the work still waiting has been waiting, p50 with p95 alongside it. It is a snapshot at the end of the window. Only tasks queued at that instant count. Work that already started counts for nothing. Read a rising p95 as “the backlog is ageing”, not “tasks took longer to start”.

## The three charts

Below the summary, three charts read the same per-bucket data:

- **Cumulative flow** — measured wait-state occupancy per time bucket, stacked. Select a band to open the task cohort behind it.
- **Arrivals and completions** — how much work entered and left the system in each time bucket.
- **Queue age** — how long the work waiting at each time bucket had already waited.

Selecting a band on any of the three opens a **Selected task cohort** list below them — the tasks behind that bucket (and that wait state, if you picked one band of a stacked bar). Each row shows the task, its type, priority, current stage, wait state, and when it was created. Read the wait state as the task’s state **in the bucket you selected** — the state it spent most of that bucket in, or the band you picked. Only **stage** is the task’s stage right now. Clear the selection to close the cohort and go back to the whole window.

## Wait states

Every instant of a task’s life, from arrival to completion (or to now, if it’s still open), falls into one of six wait states:

- **Blocked explicit** — someone filed a blocker on the task directly.
- **Blocked dependency** — the task is waiting on an unfinished upstream task.
- **Executing** — a session is actively working the task.
- **Queued** — the task is ready to run, but no session has picked it up yet.
- **Awaiting handoff** — the task is at a stage that doesn’t run an agent — a human review, or a container stage waiting on its children.
- **Unknown** — no evidence survives for that stretch of time.

The **Wait-state occupancy** table below the charts gives one row per state, with the total time this window’s tasks spent in it. The second column is a duration, not a task count. From the command line, the same six values are spelled `blocked_explicit`, `blocked_dependency`, `executing`, `queued`, `awaiting_handoff`, and `unknown`.

## Why work did not run

The **Why work did not run** table lists the placement and capacity refusals CodeHerder recorded in the window — a reason, how many times it was recorded, and a plain-language explanation of what it means. These events age out over time, so an empty table doesn’t prove nothing was ever refused, only that nothing survives in this window.

See [The Placement report](https://codeherder.com/docs/placement/) for the full picture of device eligibility behind a single task, and [Why isn’t my task moving?](https://codeherder.com/docs/task-not-moving/) if you’re trying to unstick one task right now — task flow is the aggregate, workspace-wide view of the same refusals.

## One task’s own flow

A task’s own page has a **Lifecycle flow** panel: a lane per stage attempt, showing how that attempt’s own time split across wait states. From the command line:

```
ch task flow <taskId>
```

This prints the same swimlane as a table — one row per stage attempt, with its wait state, duration, session count, and start time. Omit `<taskId>` and it defaults to `CH_TASK_ID`.

## From the command line

`ch observability` gives you the same report as three windowed commands, plus the per-task one above:

```
ch observability flow
```

Prints the summary above — arrivals, completions, WIP, completion latency, queue age, wait-state occupancy, and the refusal-reasons table — for the last 7 days by default.

```
ch observability flow buckets --window month
```

Prints one row per time bucket: arrivals, completions, WIP, and queue-age percentiles.

```
ch observability flow tasks --state queued
```

Prints the cohort task list. Narrow it with `--state` (one of the six wait states above) or `--bucket` (an RFC 3339 bucket start, copied straight from `flow buckets` ’ own output). It pages like any other `ch` list: `--limit` and `--cursor`.

`--bucket` also decides what the wait-state column means. Pass one and you get the task’s state in that bucket, the same as the **Selected task cohort** list. Leave it off and you get the task’s state as of the end of the window — its state now, for a window ending today.

All three take an optional workspace or group as their first argument, defaulting to your current workspace, and the same window flags `ch costs` uses:

```
ch observability flow my-group
ch observability flow buckets --since 30d
```

See [Understanding costs](https://codeherder.com/docs/costs/#changing-the-time-window) for how `--window` and `--since` work.

## Who can see it

Any workspace member can read the task flow report, in the app or from the CLI — there’s no separate permission to grant.

## Related guides

- [Reading the Observability report](https://codeherder.com/docs/tool-time/) — where a session’s own time went, not a task’s
- [Understanding costs](https://codeherder.com/docs/costs/) — the spend side of the same story
- [The Placement report](https://codeherder.com/docs/placement/) — why one specific device can or can’t run a task
- [Why isn’t my task moving?](https://codeherder.com/docs/task-not-moving/) — diagnose one stuck task by hand
- [Alert rules](https://codeherder.com/docs/alerts/) — get notified automatically when queue age breaches a threshold, instead of checking this report by hand
