# Reviewing output

Review in hlix works on **verified evidence**: the diff is read back from the repository host or the workspace archive, not summarized by the agent that wrote it. Comments anchor to an exact commit, and a revision request folds them into the task and re-enters the cycle.

## Prerequisites

- A task that ran through a cycle and produced a branch
- Agency role, or a client collaborator the project was shared with (comments need only view access; sending a revision needs steer)

## The evidence document

`GET /v1/api/tasks/:id/review` returns the same observation the QA evaluator reviewed.

**With the CLI:**

```bash
hlix tasks review "$TASK_ID"
```

**With the TypeScript SDK** — see [TypeScript SDK](/sdk/typescript/) for building the `hlix` client:

```ts
const evidence = await hlix.tasks.review(taskId)
```

**Over the API:**

```bash
curl -sS "$HLIX_BASE_URL/v1/api/tasks/$TASK_ID/review" \
  -H "x-api-key: $HLIX_API_KEY" \
  -H "X-Organization-Id: $HLIX_WORKSPACE_ID"
```

The fields that matter when you are deciding whether to accept the work:

| Field | Meaning |
| --- | --- |
| `source` | `github-compare` when read from the connected provider, `workspace-artifact` when the project has no remote |
| `observedAt` | When the evidence was collected — evidence is a snapshot, not a live view |
| `baseRef` / `baseSha` | The exact commit the task branch was cut from — a **commit sha, never a branch name** (see below) |
| `headRef` / `headSha` | The task branch and the reviewed commit. `headRef` is derived as `hlix/ms-<cycleId>/task-<first 12 of the task id>`. Every comment anchors to `headSha` |
| `comparison` | `{status, aheadBy, behindBy}` — `ahead`, `behind`, `identical`, `diverged`, or `unknown` |
| `filesChanged`, `additions`, `deletions`, `commits` | Change size |
| `files[]` | Per file: `path`, `previousPath`, `status`, `additions`, `deletions`, `patch`, `patchTruncated` |
| `filesTruncated` | `true` when the file list itself was cut short |
| `evaluator` | `{verdict, summary, headSha, observedAt, stale}` — `stale: true` means the evaluator judged an older commit |
| `artifact` | For repo-less projects: `{kind:"git-archive", reviewUrl, sizeBytes, capturedAt}` |
**Why the base is a commit, not a branch:** `baseRef` carries the commit sha captured when the task was dispatched, not the cycle branch's name. Each successful task branch is merged **into** the cycle branch as the last step of the build workflow — long before you open this — so a comparison against that branch by name would find the head already contained in the base and report `filesChanged: 0` for a task that changed hundreds of lines. That is an affirmative false claim, and worse than no answer: a task with no recorded branch point gets no evidence at all rather than a misleading zero.
**What the evidence deliberately does not claim:** There is no mergeability field and no CI-checks field. The evidence reports what the diff *is*, not whether a provider would merge it — a claim hlix cannot make truthfully for every repository topology it supports.

## Commenting on a line

`GET` and `POST /v1/api/tasks/:id/review/comments` hold the line-level review conversation. Every comment is anchored to `headSha`, which is what stops a comment from silently drifting onto code it was never about.

```bash
curl -sS -X POST "$HLIX_BASE_URL/v1/api/tasks/$TASK_ID/review/comments" \
  -H "x-api-key: $HLIX_API_KEY" \
  -H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
  -H 'content-type: application/json' \
  -d '{
    "headSha": "9f2c1b0ae4d5768a3c2f1e0d9b8a7c6d5e4f3a2b",
    "path": "src/routes/healthz.ts",
    "side": "head",
    "lineStart": 12,
    "lineEnd": 14,
    "body": "Read the SHA from the build env, not from git at request time."
  }'
```

The anchoring rules are enforced, not advisory:

- `headSha` is required and must be a 40-character hex commit sha.
- `body` is required, 1–10 000 characters.
- `side` requires `path`. `lineStart` requires both `path` and `side`. `lineEnd` requires `lineStart` and must be greater than or equal to it.
- Omit `path` entirely for a comment about the change as a whole.

A comment on a stale commit is refused with the commit you reviewed and the one that exists now:

```json
{"error":"The branch moved on: you reviewed 9f2c1b0…, the current head is 3d7e5a1…. Refresh the diff and comment again.","headSha":"3d7e5a1…"}
```

Listing comments returns the anchor alongside them:

```json
{
  "taskId": "b4a1f0d2-…",
  "headSha": "9f2c1b0ae4d5768a3c2f1e0d9b8a7c6d5e4f3a2b",
  "comments": [{ "id": "…", "path": "src/routes/healthz.ts", "side": "head", "lineStart": 12, "lineEnd": 14, "body": "…", "resolution": "open", "revisionIteration": 0, "stale": false }],
  "visualContext": { "enabled": false, "reasonCode": "no-live-preview", "reason": "…" }
}
```

`stale: true` on a comment means the branch has moved past the commit that comment was written against. When the head cannot be observed at all, `headSha` is `null` and every comment reads `stale: true` — that is a degraded read, not an error.

Resolve or reopen a comment with `PATCH /v1/api/tasks/:id/review/comments/:commentId` and a body of `{"resolution":"resolved"}` or `{"resolution":"open"}`. The call is idempotent.

## Requesting a revision

`POST /v1/api/tasks/:id/review/request-revision` folds every **unresolved** comment on `headSha` into the task's review feedback and re-enters the cycle, so the same worker gets your notes as instructions.

```bash
curl -sS -X POST "$HLIX_BASE_URL/v1/api/tasks/$TASK_ID/review/request-revision" \
  -H "x-api-key: $HLIX_API_KEY" \
  -H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
  -H 'content-type: application/json' \
  -d '{"headSha":"9f2c1b0ae4d5768a3c2f1e0d9b8a7c6d5e4f3a2b"}'
```

Expected result:

```json
{
  "taskId": "b4a1f0d2-…",
  "cycleId": "0a9b8c7d-…",
  "headSha": "9f2c1b0ae4d5768a3c2f1e0d9b8a7c6d5e4f3a2b",
  "commentIds": ["…"],
  "staleSkipped": 0,
  "feedback": "…the composed feedback the worker receives…",
  "iteration": 1,
  "started": true,
  "reason": null
}
```

`staleSkipped` counts comments that were anchored to an older commit and therefore left out. `started: true` is the signal that the cycle was actually re-entered.

If queuing fails, nothing is recorded — the refusal says so explicitly rather than leaving a half-applied revision:

```json
{"error":"The revision could not be queued (…), so it was not recorded. Nothing changed — try again.","started":false}
```

## Contract coverage
**Only the evidence endpoint is in the published contract:** `GET /v1/api/tasks/:id/review` appears in `openapi.json` and has an SDK method (`hlix.tasks.review`). The comment endpoints and `request-revision` do **not** appear in the published contract, so there is no generated client method for them today, and the [compatibility policy](/api/openapi/) does not yet cover them. Use the dashboard, or call them directly with an HTTP client as shown above.

## In the dashboard

The cycle queue's review panel renders the same evidence: the file list, the patch per file, and a comment thread anchored to the reviewed commit, with **Request revision** as the action that sends unresolved comments back. It is the fastest path when you are reading a diff rather than automating one.

## If review fails

- `{"error":"No review evidence: this task has no branch (it was never dispatched through a cycle)"}` — the task never ran. See [Tasks](/running/tasks/).
- `{"error":"No review evidence: this task has no recorded branch point…"}` — the task ran before a base was recorded; there is nothing to diff against.
- `{"error":"Could not fetch review evidence — …"}` with status `502` — the repository host could not be reached. Retry; nothing is wrong with the task.
- `{"error":"Task repo not found"}` — the project's repository binding is gone.
- `{"error":"The branch moved on: you reviewed …, the current head is ….","headSha":"…"}` — refresh the evidence and comment against the new head.
- `{"error":"This task has no reviewed commit …"}` with status `409` — the evidence came from a workspace archive with no commit to anchor to.
- `{"error":"This task has no cycle to retry through."}` — a standalone task cannot be revised; only cycle-dispatched work can re-enter.
- `{"error":"This task's cycle is already running. …"}` — wait for the current run before requesting a revision.
- `{"error":"Cannot anchor a comment: …"}` — the evidence itself could not be read, so no anchor exists yet.
- `{"error":"Read-only access to this project"}` — you are a client collaborator without steer rights. You may comment; you may not request a revision.

## Next steps

[Review & QA gates](/concepts/review-gates/)
  [Approvals](/running/approvals/)
  [Tasks](/running/tasks/)
  [Import trust model](/security/trust-model/)