> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cyberdesk.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Post-run Checks

> Verify exported files, screenshots, and structured output after workflow execution finishes

## Overview

Post-run Checks let you add workflow-level verification that runs **after** the main automation steps finish.

They are useful when "the workflow clicked through the UI" is not enough and you also want Cyberdesk to confirm that the run actually produced:

* the file you expected
* the screenshot you expected
* the structured output you expected
* the semantic result you expected

<Info>
  Think of Post-run Checks as a confidence-boosting double-check, not as a replacement for good workflow instructions. The workflow still does the work. Post-run Checks verify the outcome.
</Info>

## Why They Exist

In many workflows, completion is not the same thing as correctness.

Examples:

* A report download workflow may finish, but the PDF might never have been exported.
* A form-submission workflow may end on a confirmation page, but you may want a saved screenshot as proof.
* A data-extraction workflow may produce `output_data`, but you may still want to verify that the contents are complete and sensible.

Post-run Checks close that gap.

## Where They Live

Post-run Checks are defined on the **workflow**.

That means:

* you configure them once on the workflow
* every new run of that workflow can inherit them
* the run stores a snapshot of the effective checks it started with

<Note>
  Changing a workflow later does **not** rewrite historical run results. Existing runs keep the post-run check snapshot they started with.
</Note>

## When They Run

The lifecycle is:

1. The main workflow execution runs first.
2. If the run has eligible Post-run Checks, the run enters `running_checks`, which appears in the UI as **Running Checks**.
3. Cyberdesk executes the checks after the main run finishes.
4. The final terminal status is decided only after those checks complete.

For image checks that depend on run attachments, Cyberdesk may start the model verification in the background as soon as a matching attachment is saved during the run. This is only a speed optimization: the run still appears as **Running** during the main automation, switches to **Running Checks** only after the main execution finishes, and waits for any in-flight check work before deciding the final status.

<Info>
  `run_complete` and other terminal-completion semantics now mean: **the main execution finished, and any Post-run Checks finished too**.
</Info>

### Early Success Skips

Sometimes a workflow intentionally exits early from a `focused_action` because the task is already complete. In that specific path, the usual Post-run Check evidence may not exist yet: screenshots may not have been saved, files may not have been exported, and `output_data` may be incomplete.

If your focused action is explicitly allowed to call [`declare_task_succeeded`](/workflow-prompting/declare-task-succeeded), you can tell it to skip Post-run Checks for that early-success path:

```text theme={null}
Use focused_action to check whether the invoice is already marked Paid.
If it is already Paid, call declare_task_succeeded with
skip_post_run_checks=True and explain that no further action was needed.
```

When `skip_post_run_checks=True` is used, Cyberdesk does not run the configured Post-run Checks. Instead, each check snapshot is marked `success` with a message explaining that it was skipped because the focused action declared early success with Post-run Checks disabled. Cyberdesk also skips trajectory generation for that early-success branch because it is an idempotent shortcut, not the workflow's normal reusable path.

### Standalone Runs vs Sessions and Chains

Cyberdesk handles machine/session ownership differently depending on the run shape:

* **Standalone runs** usually release the machine before Post-run Checks begin.
* **Session and chain runs** can keep the machine/session claimed until Post-run Checks finish.

This matters if you are watching machine lifecycle, session closure, or downstream automation that expects a machine to be released only after the whole run is truly done.

## What Post-run Checks Are Not

Post-run Checks are **not** a complete definition of workflow success.

If you need to define a requirement where, if a certain condition isn’t met, an agent should actively try to fulfill it, define the successful action in your prompt and use a focused action to verify it, possibly setting a runtime value boolean (and output schema) to indicate success. Learn more at [`focused_action`](/workflow-prompting/focused-action) and [Generating Output Data](/concepts/generating-output-data).

Our team is working on a complete form of success criteria, which will include during and post-run checks.

## The Four Initial Check Types

Cyberdesk currently supports four initial Post-run Check types.

### 1. Attachment Exists

This check verifies that one or more expected run attachments exist.
Use it for:

* exported PDFs
* CSVs
* generated files
* saved screenshots
* one-file-per-item loop workflows

This is the simplest and most deterministic Post-run Check.

### 2. Image Check

This check asks an AI model to inspect one or more image attachments against a natural-language rule.

Use it for:

* confirmation screenshots
* chart screenshots
* receipts
* dashboards
* visual QA

Examples:

* "Verify the screenshot shows a successful payment confirmation and a visible confirmation number."
* "Verify the saved chart shows the last 30 days and includes all four vital signs."

<Tip>
  If you plan to verify screenshots visually, first create those screenshots as run attachments with [`save_screenshot_as_run_attachment`](/workflow-prompting/save-screenshot).
</Tip>

### 3. Output Data Passes Schema Validation

This is the **auto-managed** Post-run Check.

If your workflow has an output schema, Cyberdesk automatically keeps this check in sync. You do not manually author it as a separate custom check.

It verifies that the final structured `output_data` conforms to the workflow's output schema.

This is the best way to ensure:

* required fields exist
* data shape is valid
* types line up with your schema

Learn more about output transformation and schemas in [Generating Output Data](/concepts/generating-output-data).

### 4. Output Data Check

This check asks an AI model to evaluate the final structured `output_data` against a natural-language rule.

Use it when schema validation alone is not enough.

Examples:

* "Verify that every extracted invoice line item has a positive amount."
* "Verify the structured output clearly identifies a patient MRN and appointment date."
* "Verify the extracted order looks complete and not partially parsed."

<Note>
  Output-data agentic checks are most useful when you care about **semantic correctness**, not just shape correctness.
</Note>

## Attachment Targeting Modes

The two attachment-based checks, **Attachment Exists** and **Image Check**, support three targeting modes.

### Exact Filenames

Use this when you know the exact attachment names ahead of time.

Examples:

* `invoice.pdf`
* `confirmation.png`
* `daily_report.csv`

Best for:

* stable filenames
* deterministic exports
* named screenshots

#### Exact Mode Behavior

* You provide one or more filenames.
* Filenames should include file extensions.
* At runtime, Cyberdesk looks for those exact attachment filenames.
* If a required attachment is missing, the check fails.

<Info>
  In the workflow editor, Cyberdesk may suggest exact filenames by scanning your prompt for known attachment-producing patterns such as [`save_screenshot_as_run_attachment`](/workflow-prompting/save-screenshot) and [`mark_file_for_export`](/workflow-prompting/mark-file-for-export). These are suggestions only. You can always add or edit filenames manually.
</Info>

### Regex

Use this when the filename changes between runs, but follows a consistent pattern.

Examples:

* `^invoice_.*\.pdf$`
* `^receipt_[0-9]{8}\.png$`
* `^export_.*\.csv$`

Best for:

* timestamps
* generated IDs
* date-based filenames
* dynamic naming conventions

#### Regex Mode Behavior

* Matching is done against the attachment **filename**.
* Matching is **full-string** and **case-sensitive**.
* Zero matches fail the check.
* You can optionally specify an expected match count.

Expected match count can come from:

* a literal integer
* an input or sensitive reference that resolves to an integer
* an input or sensitive reference that resolves to an array, in which case Cyberdesk uses the array length

### Loop Items

Use this when the workflow should produce **one attachment per loop item**.

Examples:

* `receipt_{{loop_item.name}}.png`
* `claim_{{loop_item.claim_id}}.pdf`
* `summary_{{loop_item}}.csv`

Best for:

* one-file-per-item workflows
* iterating over arrays
* exporting a file for each selected record

#### Loop Items Mode Behavior

You provide:

* `loop_input`
* `loop_item_filename_template`
* optionally, **Optional loop attachments**

Cyberdesk resolves the expected filenames by applying the template once per item in the loop input.

The `loop_input` semantics match the looping system:

* a JSON array means "iterate over this array"
* an integer `n` means "iterate over items `0..n-1`"

If the loop input resolves to an unsupported value, the check fails with a configuration/data error.

When **Optional loop attachments** is enabled, missing attachments for individual loop items are skipped instead of failing the check. Use this when a loop only sometimes saves an attachment for an item. The check still evaluates any matching attachments it does find. If every loop item is skipped because no matching attachments exist, the check succeeds and records a message explaining that all items were skipped.

Learn more about loop semantics and `{{loop_item}}` in [Looping Tools](/workflow-prompting/looping-tools).

<Note>
  Need literal template-style text in a Post-run Check prompt or filename template? Escape the opening delimiter:

  * `\{customer_name}` renders as literal `{customer_name}`
  * `\{{current_status}}` renders as literal `{{current_status}}`
  * `\{$support_pin}` renders as literal `{$support_pin}`

  This works anywhere Post-run Checks accept templated text, including check prompts and filename templates.
</Note>

## Producing Attachments That Checks Can Validate

Post-run Checks work on **run attachments**, not arbitrary machine state.

That means your workflow should intentionally produce the attachments you want to verify.

### For screenshots

Use [`save_screenshot_as_run_attachment`](/workflow-prompting/save-screenshot).

Good for:

* confirmation pages
* receipts
* dashboards
* visual evidence

### For files created or downloaded on the machine

Use [`mark_file_for_export`](/workflow-prompting/mark-file-for-export).

Good for:

* PDFs
* CSVs
* generated documents
* downloaded exports

<Tip>
  If you plan to add an attachment-based Post-run Check, it is usually worth naming your saved screenshot or exported file clearly and consistently. Predictable filenames make exact targeting much easier than regex.
</Tip>

## Writing Good Check Prompts

The AI-based checks are:

* **Image Check**
* **Output Data Check**

The best prompts are narrow and outcome-focused.

### Good prompt characteristics

* State the pass condition clearly.
* Mention the evidence that matters.
* Say what should count as failure.
* Avoid asking the model to judge unrelated parts of the workflow.

### Better Image Check Prompt

```text theme={null}
Verify the screenshot shows a successful submission confirmation. The page should
include a visible confirmation number and should not show any validation or error
messages.
```

### Better Output Data Check Prompt

```text theme={null}
Verify the output data looks complete. Every invoice line item should have a
description, quantity, unit price, and positive total amount.
```

### Weaker Prompts to Avoid

```text theme={null}
Check that this looks good.
```

```text theme={null}
Make sure nothing is wrong.
```

These are too vague and produce less reliable results.

## How Statuses Work

When Post-run Checks exist, the run may enter **Running Checks** after the main workflow steps are done.

That means:

* the automation steps may be finished
* the run is **not terminal yet**
* Cyberdesk is still verifying the outcome

### Final status behavior

If the main execution succeeded:

* all checks pass → final run status is `success`
* one or more checks fail → final run ends on a failure path
* a check hits an infrastructure-level problem → final run ends on `error`

If the main execution already ended in `error` or `task_failed`:

* Cyberdesk can still record Post-run Check results for observability
* the final run status stays on that failure path

If the run is cancelled:

* cancelled before checks start → checks are skipped
* cancelled during Running Checks → unfinished checks are cancelled

<Warning>
  Some deployments map failed checks to `task_failed`, while others map them to `error` for compatibility with existing integrations. Either way, the run did **not** pass its verification criteria.
</Warning>

### Cleanup prompts after check failure

Workflows can include one optional, workflow-level **Post-run Check failure cleanup prompt**. Cyberdesk only runs this prompt when one or more Post-run Checks fail during a session or chained run, after the post-run check results are known and before the remaining queued runs are cancelled.

Use cleanup prompts for recovery steps that should happen when any verification failure would otherwise leave downstream session steps in a bad state, such as closing a modal, undoing a partially submitted record, logging out, or returning the app to a safe page. Leave it blank for standalone runs or workflows where no machine-side cleanup is needed.

When cleanup runs, Cyberdesk appends a marker to `run_message_history` with `event_type: "post_run_check_failure_cleanup_start"`. The run details page uses this marker to jump from the Post-run Checks section and timeline navigation to the independent cleanup agent's message history. Screenshots captured after the marker are labeled as cleanup screenshots in the filmstrip viewer.

## Accessing Results in Code

Post-run Check results live on `run.post_run_checks`.

You can access them from:

* `get_run`
* `run_complete` webhook payloads
* `list_runs` when you request `fields=post_run_checks`

Important details:

* `run.post_run_checks` is an **array**, not an object keyed by check name
* each item includes `name`, `status`, `error_message`, `messages`, and `matched_filenames`
* if you want to look checks up by name, keep those names stable and unique within the workflow
* the best place to react automatically is the `run_complete` webhook, because it fires only after Post-run Checks finish

<Tabs>
  <Tab title="TypeScript">
    ```ts theme={null}
    const checks = run.post_run_checks ?? []

    const checksByName = new Map(
      checks.filter((check) => check.name).map((check) => [check.name!, check]),
    )

    const invoiceCheck = checksByName.get("Invoice PDF exists")

    if (invoiceCheck && invoiceCheck.status !== "success") {
      console.log("status:", invoiceCheck.status)
      console.log("error:", invoiceCheck.error_message)
      console.log("messages:", invoiceCheck.messages)
      console.log("matched files:", invoiceCheck.matched_filenames)
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    checks = run.post_run_checks or []
    checks_by_name = {check.name: check for check in checks if check.name}

    invoice_check = checks_by_name.get("Invoice PDF exists")
    status = getattr(invoice_check.status, "value", invoice_check.status) if invoice_check else None

    if invoice_check and status != "success":
        print("status:", status)
        print("error:", invoice_check.error_message)
        print("messages:", invoice_check.messages)
        print("matched files:", invoice_check.matched_filenames)
    ```
  </Tab>
</Tabs>

<Info>
  If you are polling runs via `list_runs`, remember to request `post_run_checks` explicitly in the `fields` list. `get_run` and `run_complete` already include it.
</Info>

## Example End-to-End Pattern

Imagine a workflow that:

1. downloads an invoice PDF
2. saves a confirmation screenshot
3. extracts structured invoice data

You might configure:

* **Attachment Exists**
  * exact filename: `invoice.pdf`
* **Image Check**
  * exact filename: `invoice_confirmation.png`
  * prompt: "Verify the screenshot shows a successful invoice export with no visible errors."
* **Output Data Passes Schema Validation**
  * auto-managed because the workflow has an output schema
* **Output Data Check**
  * prompt: "Verify the structured output contains invoice number, invoice date, vendor name, and a positive total."

This gives you both:

* deterministic checks on concrete artifacts
* semantic checks on the final output

## Best Practices

1. Prefer **exact filenames** when names are stable.
2. Use **regex** only when the variability is real and predictable.
3. Use **loop\_items** when the workflow intentionally produces one attachment per item, and enable optional loop attachments only when some loop items legitimately produce no attachment.
4. Pair **schema validation** with **output data checks** when you care about both shape and meaning.
5. Keep AI check prompts narrow and specific.
6. Add a workflow-level cleanup prompt in sessions or chains when any failed verification needs machine-side cleanup before the session ends.
7. Treat Post-run Checks as verification, not as a substitute for good workflow instructions.
8. Review check results in run details when tuning a workflow.

## Related Docs

* [Save Screenshot as Run Attachment](/workflow-prompting/save-screenshot)\
  Learn how to create screenshot attachments that image checks can evaluate.

* [Mark File for Export](/workflow-prompting/mark-file-for-export)\
  Learn how to export files from the machine as run attachments.

* [Looping Tools](/workflow-prompting/looping-tools)\
  Learn how `start_loop`, `{{loop_item}}`, and loop inputs work.

* [Generating Output Data](/concepts/generating-output-data)\
  Learn how output schemas and `output_data` are produced and validated.

* [Declare Task Succeeded](/workflow-prompting/declare-task-succeeded)\
  Learn how early success detection during main execution differs from post-run verification.
