# Debugging

> Diagnose why your agent isn't responding, tool calls are failing, or deploys are stuck — in order from most-likely cause to least.

## Debugging Functions

When your function isn't behaving, the issue is usually in one of these layers:

```
┌─────────────────────────────────────────────────┐
│ 1. Inbound message               (does it arrive?)
├─────────────────────────────────────────────────┤
│ 2. Agent dispatch                (does the agent run?)
├─────────────────────────────────────────────────┤
│ 3. LLM call                      (does the LLM respond?)
├─────────────────────────────────────────────────┤
│ 4. Tool invocation               (does the tool get called?)
├─────────────────────────────────────────────────┤
│ 5. Tool handler                  (does YOUR code work?)
└─────────────────────────────────────────────────┘
```

Walk the layers top-to-bottom. The first one that fails is the one to fix.

**Tip:**
If you use Claude Code / Cursor / Copilot, install [Zavu's Coding Agent Skills](https://www.zavu.dev/docs/tools/coding-agent-skills)
and ask your assistant "why isn't my agent responding?" — it'll run the
diagnostic commands below in order and interpret the output.

```sh
npx skills add zavudev/zavu-skills
```

## 1. Did the inbound message arrive?

Open the dashboard → **Senders** → your sender → **Conversations**.

- **No message visible**: the inbound never reached Zavu. Check your sender's
  webhook (WhatsApp, Telegram) is wired up. The dashboard shows webhook
  config and recent delivery attempts.
- **Message visible, no reply**: inbound is fine, problem is downstream.
  Go to step 2.

## 2. Did the agent run?

```sh
npx zavudev agents executions list --sender <senderId>
```

This is the most useful debug command. Three possible states:

### a) "No executions."

The inbound arrived but the agent dispatcher didn't even try to invoke the
agent. Causes:

| Likely cause | Check |
|---|---|
| Wrong `SENDER_ID` secret on the function | `npx zavudev fn secrets list` — last 4 chars of the value must match the senderId you're testing |
| Agent disabled | `npx zavudev agents get --sender <senderId>` → `enabled` must be `true` |
| Channel filtered out | `triggerOnChannels` doesn't include the inbound's channel. Use `["*"]` or include the specific one (e.g. `"telegram"`) |
| Message type filtered out | `triggerOnMessageTypes` excludes the type (e.g. you sent an image and only `"text"` is allowed) |
| Empty message text | The dispatcher skips messages with no text. Inbound types like reactions or system events don't trigger agents |

### b) `status: error`

The agent ran but something blew up. Fetch the full detail:

```sh
npx zavudev agents executions get <executionId> --sender <senderId>
```

You'll see `errorMessage` and `errorCode`. Common error categories:

| `errorCode` | Meaning | Fix |
|---|---|---|
| `balance_insufficient` | Provider `zavu` ran out of AI gateway balance | Top up balance in dashboard, or switch to BYOK by setting `provider` + `apiKey` in `defineAgent` |
| `rate_limited` | Hit the LLM provider's rate limit | Lower concurrency or wait |
| `provider_error` | LLM provider rejected the call | Check the message text — sometimes prompts trigger safety filters. Also verify your `model` ID is valid for the provider |
| `tool_not_found` | Agent tried to call a tool that doesn't exist | Tool name in `defineTool` doesn't match what the LLM is calling. Or the deploy didn't reconcile — run `npx zavudev deploy` again |
| `tool_error` | A tool handler threw | Check the tool's source. The `errorMessage` includes the handler's exception text |

### c) `status: success` but no message sent back

The agent succeeded but didn't reply. Look at `responseText` in the execution
detail — if it's empty, the LLM returned nothing useful. Adjust your
`systemPrompt` to encourage replies, or check that the model isn't returning
just tool calls without a final response.

## 3. Is the LLM working?

If executions are failing with `provider_error`:

```sh
npx zavudev agents executions get <executionId> --sender <senderId> --json
```

Look at the full `errorMessage`. Typical issues:

- **Bad model ID**: e.g. `openai/gpt-4o-mini` works on `provider: "zavu"` (our
  gateway uses prefixed IDs), but the raw `gpt-4o-mini` works on
  `provider: "openai"` directly.
- **Expired API key** (when using `provider: "openai"` etc.): rotate the key
  and update the agent's `apiKey`.
- **Tool schema malformed**: rare, but if your `defineTool` parameters JSON
  Schema has a typo, the LLM's tool-calling step can fail. Validate the
  schema renders to valid JSON.

## 4. Did the tool get called?

Once the agent runs successfully but you suspect the tool wasn't invoked,
check the **function logs**:

```sh
npx zavudev fn logs --tail
```

Then send a message that should trigger the tool. You should see:

```
[ctx.log] <your console.log output>
```

If you see nothing in the function logs but `agents executions` shows
success, the LLM chose not to call the tool. Tighten the tool's
`description` so the LLM understands when to call it.

## 5. Is the tool handler buggy?

Once you confirm the tool is being called, isolate it locally:

```sh
npx zavudev fn invoke --tool view_menu --args '{"filter":"vegan"}'
```

This runs the handler with synthetic args and prints the result. Iterate
locally without redeploying.

For deeper debugging, add `ctx.log` calls inside the handler:

```ts
defineTool({
  name: "lookup_order",
  // ...
  handler: async (args, ctx) => {
    ctx.log("lookup_order called with", args)
    const order = await fetchOrder(args.orderId)
    ctx.log("found order", { id: order?.id, status: order?.status })
    return order
  },
})
```

Then `npx zavudev deploy` and `npx zavudev fn logs --tail` while triggering the tool from
a real conversation. The `[ctx.log]` lines appear in the live tail.

## Reconcile didn't pick up my new agent / tool

When you run `npx zavudev deploy`, the platform reads your `defineAgent` /
`defineTool` declarations from a `manifest probe` Lambda invocation, then
reconciles the live agent + tools to match. If you see:

```
! manifest probe: manifest probe threw: defineAgent: senderId is required.
```

…the issue is your code threw at module load time. Common causes:

- **Missing required secret**: e.g. `process.env.SENDER_ID!` evaluates to
  `undefined` because you didn't set the secret yet. Fix:
  `npx zavudev fn secrets set SENDER_ID <value>` then `npx zavudev deploy`.
- **Syntax error in your `index.ts`**: the bundler caught it earlier but
  some edge cases get through. Run `tsc --noEmit` locally before deploying.

## Local invocation cheat sheet

| Goal | Command |
|---|---|
| Test a single tool handler | `npx zavudev fn invoke --tool <name> --args '{...}'` |
| Test an event handler (`defineFunction`) | `npx zavudev fn invoke --event message.inbound --data '{...}'` |
| Tail live production logs | `npx zavudev fn logs --tail` |
| Query recent agent runs | `npx zavudev agents executions list --sender <senderId>` |
| Full detail of one run | `npx zavudev agents executions get <executionId> --sender <senderId>` |
| Inspect the agent config | `npx zavudev agents get --sender <senderId>` |
| Inspect tools attached | `npx zavudev agents tools list --sender <senderId>` |
| See what secrets are set | `npx zavudev fn secrets list` |

## When all else fails

1. Add `ctx.log("got here", { someState })` aggressively in your handler.
2. `npx zavudev deploy` and `npx zavudev fn logs --tail` simultaneously.
3. Trigger the flow from a real conversation.
4. Read the logs top-to-bottom — the first absence of expected output is
   the layer that's failing.
5. Open a ticket with the function ID, the `executionId` that failed, and
   the timestamp. Support can pull internal traces faster than guessing.
