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

# Read logs and debug an agent

> Use lua logs, isolated sandbox threads, and LUA_DEBUG to find out why a tool, job, webhook, or workflow misbehaved

After this guide, you can go from "the agent answered wrongly" to the exact log entry that explains it, and fix the code once instead of pushing blind. The loop is the same in the [sandbox](/concepts/environments) and in production: reproduce with one message, read the entries that run wrote, change the code, repeat. For the pre-release rungs (`lua test`, workflow drivers, voice tests) see [Test an agent before you release](/ship/testing).

*Verified against lua-cli 3.33.0.*

**Before you begin**

* A project signed in with `lua auth configure`; `lua logs` reads the agent in `lua.skill.yaml` (`--agent-id <id>` overrides it for another agent you administer).
* Something to inspect: a conversation, a job run, a webhook call, or a workflow run that already happened.

<Steps>
  <Step title="Reproduce on an isolated thread">
    Send the smallest message that triggers the code path, on a fresh thread so earlier turns cannot influence the answer. The sandbox runs sandbox versions compiled from your local code; production runs the promoted version.

    ```bash theme={null}
    lua chat -e sandbox -m "What is the status of my tickets? My email is user@example.com" -t --clear
    ```

    When the bug is in one function rather than in the model's decision, skip the conversation and call the function with `lua test skill --name <tool-name> --input '<json>'`; its output is exactly what the tool returned, with no log lookup needed.
  </Step>

  <Step title="Read the entries that run wrote">
    Filter by primitive type and, for skills, jobs, webhooks, processors, and devices, by name. Entries print oldest first.

    ```bash theme={null}
    lua logs --type skill --name tickets --limit 2
    ```

    ```text Output theme={null}
    📊 Skill Logs

    ────────────────────────────────────────────────────────────────────────────────
    Page 1 of 13 (26 total logs)

    🔍 [12/09/2026, 13:58:53] DEBUG
       Skill Name: tickets
       Skill ID:   0c7927ec-ffc5-40ad-b76e-63cba81e1390
       Tool Name: lookup_tickets
       Tool result []
       ------------------------------------------------------------------------------
    🔍 [12/09/2026, 13:58:53] DEBUG
       Skill Name: tickets
       Skill ID:   0c7927ec-ffc5-40ad-b76e-63cba81e1390
       Tool Name: lookup_tickets
       Calling tool with input {"customerEmail":"stefan@heylua.ai"}
    …
    ```

    Each tool call writes `Calling tool with input …`, then `Execute function completed in <n> ms` and `Tool result …`, or an `ERROR` entry with the thrown message. Anything your code prints with `console.log` appears as a `DEBUG` entry and `console.error` as an `ERROR` entry, so log the raw return value of a platform call before you transform it and read its real shape here. A message field is stored up to 256 KB and then cut with `[truncated; original length <n>]`.
  </Step>

  <Step title="Widen or narrow the filter">
    `--type` accepts `all`, `skill`, `job`, `webhook`, `preprocessor`, `postprocessor`, `device`, `device-trigger`, `user_message`, `agent_response`, `agent_error`, `mcp`, `rag`, `runtime`, and `calls`. `--name` works with `skill`, `job`, `webhook`, `preprocessor`, `postprocessor`, and `device`. `agent_error` holds failures on the message path such as billing, validation, and model errors; `runtime` holds the agent runtime's own lines; `user_message` and `agent_response` show what was said, with the channel and end user ID.

    ```bash theme={null}
    lua logs --type agent_error --limit 20
    lua logs --type webhook --name ticket-status-webhook --limit 5
    lua logs --type all --user-id <user-id> --limit 50 --page 2
    ```

    `--limit` defaults to 20 and the server caps it at 100; use `--page` for older entries. There is no time filter and no environment filter: sandbox and production entries are stored together, and `metadata.channel` is `dev` for every message sent from `lua chat` in either environment, so bound a check by `timestamp` and, for one conversation, by `--user-id`. The flag table is on the [`lua logs` reference](/reference/cli/logs).
  </Step>

  <Step title="Script it with --json">
    `--json` returns `{ logs, pagination }`. Each entry carries `subType` (`error`, `warn`, `info`, `debug`, `start`, or `complete`; there is no `level` field), `message`, an optional `duration` in milliseconds, and `metadata` with `logSource`, `primitiveName`, `primitiveId`, `toolName`, `userId`, and `channel`.

    ```bash theme={null}
    lua logs --type postprocessor --limit 1 --json
    ```

    ```text Output theme={null}
    {
      "logs": [
        {
          "_id": "6aa54ccd47f5e6a851ccf9fe",
          "id": "1789217997644-jtoulxxxq",
          "timestamp": "2026-09-12T12:59:57.644Z",
          "type": "log",
          "subType": "debug",
          "message": "Execute function completed in 0 ms",
          "metadata": {
            "userId": "9029d3f6-3d88-487f-a5ef-4d866059d9f6",
            "agentId": "baseAgent_agent_1789214224176_2vta8rnyn",
            "logSource": "postprocessor",
            "primitiveId": "postprocessor_d39f9879-b73b-4a71-9032-40eeab2ad429",
            "primitiveName": "ticket-footer",
            "toolId": null,
            "toolName": null,
            "channel": "dev"
          }
        }
      ],
      "pagination": {
        "currentPage": 1,
        "totalPages": 16,
        "totalCount": 16,
        "limit": 1,
        "hasNextPage": true,
        "hasPrevPage": false,
        "nextPage": 2,
        "prevPage": null
      }
    }
    ```

    Select errors with `jq '.logs[] | select(.subType == "error")'`.
  </Step>

  <Step title="Debug the CLI itself">
    When a command fails, it prints one line, `✖ <code>: <message>`, plus a `💡` hint. Set `LUA_DEBUG=1` (or pass `--debug` to `lua compile`) to print the stack trace under that line.

    ```bash theme={null}
    LUA_DEBUG=1 lua push all --ci --force
    ```

    The exit code tells you the class of failure before you read the message; see [Errors and exit codes](/reference/cli/errors-and-exit-codes).
  </Step>

  <Step title="Verify a deploy">
    After `lua version promote` or `lua deploy`, note the time, send one production message on a fresh thread, and confirm that nothing was written since under `agent_error` or as an `error` under `skill`. A throwing tool lands under `skill`, not `agent_error`, and the logs have no time filter, so the check reads both types and bounds them by `timestamp`.

    ```bash theme={null}
    since=$(date -u +%Y-%m-%dT%H:%M:%SZ)
    lua chat -e production -m "What is the status of my tickets? My email is user@example.com" -t --clear
    lua logs --type agent_error --limit 50 --json --ci | jq -e --arg s "$since" '[.logs[] | select(.timestamp > $s)] | length == 0'
    lua logs --type skill --limit 50 --json --ci | jq -e --arg s "$since" '[.logs[] | select(.timestamp > $s and .subType == "error")] | length == 0'
    ```

    `jq -e` exits 1 when the filtered array is not empty, which is what a CI job needs; see [Automate releases in CI](/ship/ci-and-automation).
  </Step>
</Steps>

## Options you may need

### Read trigger, job, workflow, and call records

Some primitives keep their own execution records in addition to `lua logs`:

```bash theme={null}
lua triggers logs --trigger <trigger-name> --limit 20 --json
lua jobs history -i <job-name>
lua workflows status <run-id> --steps
lua workflows logs <run-id> --step <step-id>
lua workflows job-logs <run-id> <step-id> --tail 200
lua logs --type calls --direction outbound --status failed --json
```

`lua triggers logs` shows up to 200 executions; `lua jobs history` shows the last 20 with status, duration, result, and error; `lua workflows status --steps` prints per-step state and output previews, `lua workflows logs` the run's event stream (`--follow` keeps it open), and `job-logs` the container output of a Job-tier step. See the [`lua triggers`](/reference/cli/triggers), [`lua jobs`](/reference/cli/jobs), and [`lua workflows`](/reference/cli/workflows) references.

### Keep secrets out of the logs

The runtime's own entries (`runtime`, workflow events, device command audits) redact bearer tokens, JWTs, and `token=`, `api_key=`, `secret=`, and `password=` values, and device command payload fields such as `content` and `password` are replaced with byte counts. Your own `console.log` output is stored as printed: do not log `env('…')` values or request headers. Local `lua test` runs write no server entries at all.

## If it isn't working

<Accordion title="✖ usage: --type is required when using --name">
  `--name` filters an entity list, so the CLI needs to know which list. Pass `--type skill|job|webhook|preprocessor|postprocessor|device` with it; message types (`user_message`, `agent_response`, `agent_error`, `runtime`, `mcp`, `rag`) have no names.
</Accordion>

<Accordion title="✖ usage: lua: &#x22;mastra&#x22; is not a valid logs.type value.">
  The hint lists the accepted values. `--type all` shows everything; `runtime` is the type for agent runtime and model-provider lines.
</Accordion>

<Accordion title="✖ not_found: Skill &#x22;<name>&#x22; not found">
  Exit 3. `--name` takes the primitive's name as it appears on the server (`lua skills view`, `lua jobs view`, `lua webhooks view`), or its ID. For a skill it is the skill name, not a tool name.
</Accordion>

## Next steps

<Columns cols={2}>
  <Card title="lua logs reference" href="/reference/cli/logs">Every filter and the entry schema.</Card>
  <Card title="Test an agent before you release" href="/ship/testing">Reproduce with exact input before reading logs.</Card>
  <Card title="Troubleshoot the CLI" href="/ship/troubleshooting">Exact error strings by stage, with fixes.</Card>
  <Card title="About security and data" href="/concepts/security-and-data">Where logs live and what is redacted.</Card>
</Columns>
