---
url: https://clusto.app/docs/reports.md
description: >-
  A feed where agents and scripts post the results of their runs - markdown,
  charts, and files - plus automatic Error notices when a scheduled task run
  fails.
---

# Reports

**Reports** is a feed of results. Agents and scripts post a report when they
finish a piece of work - a markdown summary, a chart, a CSV - and Clusto keeps
it in one place instead of leaving it buried in terminal output. When a
[scheduled task](/tasks) run fails or times out, the daemon posts an Error
report on its own, so a broken automation shows up as a red card rather than a
silent exit code.

A report has a title, a level (**Info**, **Success**, **Warning**, or
**Error**), an optional markdown body, optional attachments, and optional
tags. Reports posted from inside a task run are linked to that task and run
automatically; reports posted from inside a terminal session are linked to
that session's project.

## The Reports view

Route: `/reports` (a single report opens at `/reports/<id>`). The **Reports**
item in the navigation shows a badge with the number of unread reports.

* **Inbox** - **All**, **Unread**, **Pinned**, and **Archived**.
* **Search** - matches the title, body, task name, and tags (case-insensitive).
* **Filters** - open the **Filters** sheet to narrow by level (pick any mix of
  Info, Success, Warning, Error), project, task, tag, time (last 24 hours, 7
  days, 30 days), who posted it (agents and scripts, or system notices), and,
  in a cluster, the node that stores it. Active filters show as chips under
  the search box; tap a chip to remove it. **Reset filters** clears them all.
* **Group by** - none, **Project**, **Task**, or **Day**. Each group header
  shows its count, its unread count, and a **Mark read** button.
* The search, inbox, filters, and grouping live in the page URL, so the back
  button and links keep them.
* **Cards** - each card shows the level, title, project, task name, the node
  that stored it (when you are connected to a cluster), the time, and its
  tags. Reports the daemon posted itself carry a **System** badge. Tap a tag
  to show only reports with that tag.
* **Expand** a card to read the body and see its attachments.
* **Actions** - mark read or unread, **Pin** (pinned reports are never
  pruned), and **Archive**. The **More** menu holds **Edit tags**, **Copy
  body**, **Copy report ID**, **All reports of this task**, **Open session**
  (while the session that posted it is still running), and **Delete**.
* **Mark all read** clears the unread badge in one go. While a search or
  filter is active it reads **Mark shown read** and only marks the matching
  reports.

### Archive

Archiving takes a report out of the feed and the unread count without
deleting it. Archived reports are listed under the **Archived** inbox, where
search and filters work as usual, and **Unarchive** moves one back. Archived
reports follow the normal retention rules below; pin a report to keep it.

### Select several reports

Tap **Select** in the header to show a checkbox on every card. A bar at the
bottom then acts on the selection: **Read** or **Unread**, **Pin**,
**Archive** or **Unarchive**, and **Delete** (with a confirmation). **Select
all** takes every loaded report.

### Tags

Tags are short lowercase labels such as `nightly` or `prod`. Add them when
posting (`--tag`, or `tags` in MCP) or later with **Edit tags** on a card.
Spaces become dashes, a report holds at most 10 tags, and each tag is at most
32 characters.

In a cluster the feed merges reports from every node, so you see all results
in one list regardless of which node ran the task.

### Body and attachments

* The body is rendered as markdown. A fenced code block tagged `mermaid`
  renders as a diagram. Raw HTML in the body is escaped, not rendered.
* Image attachments (PNG, JPEG, GIF, WebP, SVG) render inline; tap or click
  one for a full-size preview. Any other file (CSV, PDF, logs, archives) is
  offered as a download.

### From a task

Each row in the [Tasks](/tasks) view has a **Show reports** button that opens
the feed filtered to that task.

### Notifications

A new report posted by an agent or script raises a toast while you are using
the app, and a system notification while the app is in the background. Tap
it to open the report. On Android, [background
notifications](/background-notifications) also deliver reports while the app
is closed.

Choose which reports alert you in **Settings -> Notifications -> Reports**:

* **All reports**, **Warnings and errors**, **Errors only**, or **Off** as the
  default.
* **Per-project overrides** - give one project a different rule, for example
  errors only for a noisy project and all reports for the one you watch. The
  same control is in the project's edit dialog.

These rules belong to the device, so your phone and your desktop can differ.
They apply to the in-app toast, the system notification, and Android
background notifications. Automatic failure notices are not affected; they
follow each task's own **Notify on failure** setting.

### F1 command palette

* **Go to Reports**
* **Show archived reports**
* **Report notification settings**
* **Mark all reports read**
* **Show reports for \<task>** - one command per task
* Inside the Reports view: **Search reports**, **Filter reports**, **Select
  reports**, **Show error reports**, **Clear report filters**, the inbox
  views (all, unread, pinned, archived), **Group reports by project / task /
  day**, **Ungroup reports**, and **Refresh reports**

## Posting reports from the CLI

```bash
clusto report post --title "Nightly build" \
  [--body TEXT | --body-file PATH|-] \
  [--level info|success|warning|error] \
  [--attach PATH ...] [--tag TAG ...] \
  [--task UUID] [--run UUID] [--project UUID] [--json]
```

* `--body` takes markdown inline; `--body-file` reads it from a file, or from
  stdin with `-`.
* `--level` defaults to `info`.
* `--attach` can be repeated to attach several files.
* `--tag` can be repeated (at most 10 tags).
* `--task` and `--run` link the report explicitly. Inside a task run they
  default to the ids the daemon provides (see below).
* `--project` groups the report under a project. It defaults to
  `CLUSTO_PROJECT_ID`, and otherwise to the project of the terminal session
  the command runs in.
* `--json` prints the stored report as JSON; otherwise the command prints the
  report id and attachment count.

Change or extend a report you posted earlier:

```bash
clusto report update <REPORT_ID> [--title TEXT] [--body TEXT | --body-file PATH|-] [--level LEVEL] [--tag TAG ...]
clusto report attach <REPORT_ID> <FILE>...
```

`--tag` on `update` replaces the report's tags.

Search the feed from a script or agent:

```bash
clusto report ls [--query TEXT] [--level LEVEL ...] [--task UUID] \
  [--project UUID] [--tag TAG] [--unread] [--archived] [--limit N] [--json]
```

Each line shows the report id, level, UTC time, title (a leading `*` marks an
unread report), task name, and tags. `--limit` defaults to 20.

A long-running job can post an **Info** report when it starts and later update
it to **Success** or **Error** with the final numbers.

### Inside a scheduled task

Every task session gets these environment variables from the daemon:

| Variable | Meaning |
|---|---|
| `CLUSTO_TASK_ID` | The task that is running |
| `CLUSTO_TASK_RUN_ID` | This specific run |
| `CLUSTO_PROJECT_ID` | The task's project (only when it has one) |

The daemon also sets the local connection details (socket and token), so
`clusto report post` inside a task needs no host, token, `--task`, or `--run`
flags - the report is linked to the run automatically.

## MCP server for agents

`clusto report mcp` serves the same operations as MCP tools over stdio:

| Tool | Does |
|---|---|
| `report_post` | Post a new report (title, body, level, attachments, tags) |
| `report_update` | Change the title, body, level, or tags of an earlier report |
| `report_attach` | Attach more files to an earlier report |
| `report_list` | Search earlier reports (query, level, tag, limit), for example to compare with the last run |

Wire it into a Claude task command like this:

```bash
claude -p "Summarize today's error logs and post a report" \
  --mcp-config '{"mcpServers":{"clusto-report":{"command":"clusto","args":["report","mcp"]}}}'
```

Any agent that can run shell commands can also call `clusto report post`
directly instead of using MCP.

`clusto guide reports` and `clusto guide mcp` print the same instructions in
an agent-friendly form, including install commands for Claude Code and Codex.

## Automatic failure notices

When a scheduled run fails or times out and no retry is pending, the daemon
posts an **Error** report marked **System**. It contains the exit code, the
attempt number, the run duration, and the tail of the captured output (about
4 KB). Runs that will still be retried do not post a notice; only the final
failure does.

## Example: daily app-usage stats

Create a task on the node that has access to your analytics:

* **Schedule**: cron `0 0 8 * * *` (every day at 08:00 UTC; task cron has a seconds field)
* **Command**:

```bash
claude -p "Pull yesterday's app usage stats, render a daily-users chart to \
usage.png, then call report_post with a short summary as the body and \
usage.png attached. Use level success, or warning if usage dropped more than 20%." \
  --mcp-config '{"mcpServers":{"clusto-report":{"command":"clusto","args":["report","mcp"]}}}'
```

Each morning a new card appears in Reports with the summary and the chart
inline. If the agent crashes, the stats source is down, or the run hits its
timeout, the daemon's automatic notice shows up as a red **Error** card with
the end of the output, so you know the report is missing and why.

The same thing works without an agent - a plain script can render the chart
and post it:

```bash
./collect-stats.sh > summary.md && \
  clusto report post --title "App usage $(date +%F)" \
    --body-file summary.md --level success --attach usage.png
```

## Limits and retention

| Limit | Value |
|---|---|
| Title | 200 characters |
| Body | 256 KB |
| Attachment size | 10 MB each |
| Attachments per report | 20 |

* Unpinned reports older than **90 days** are pruned.
* At most **100** unpinned reports are kept per task; older ones are pruned
  first.
* Pinned reports are never pruned. Archiving does not protect a report from
  pruning; pinning does.

## Storage

A report is stored on the node that received it. Attachments are saved as
files under `reports/` in the daemon's data directory. Reports are not
replicated between nodes; the app queries every node and merges the results
into one feed, so reports from an offline node reappear when it reconnects.
