# Error Handling (/docs/workflows/error-handling)

> For the complete documentation index, see [llms.txt](https://docs.tandemcx.ai/llms.txt). Any page on this site can be fetched as Markdown by appending `.md` to its URL.

Real conversations don't always go to plan: an API is slow, a caller doesn't
respond, a value isn't what you expected. Account for these paths instead of leaving them
unwired. A node's **exits** are the alternate paths it exposes when something doesn't take
the happy path.

## Timeouts on external calls

The **Integration**, **Invoke Lambda**, and **SharpenDB** nodes expose a `timeout` exit that
the flow takes when the call doesn't complete in time:

- **Integration** — the node's **Timeout**, capped at 25 seconds whatever you enter.
- **Invoke Lambda** — the node's Timeout plus one second, and never more than 28 seconds, so any
  Timeout of 27 or more takes `timeout` at 28 seconds.
- **SharpenDB nodes** — 20 seconds. The node's Timeout field is not applied and does not change
  that limit.

An Invoke Lambda function that hits its own configured Lambda timeout before the node stops
waiting takes `fallback` instead. See
[AWS](/docs/workflows/integrations/aws#when-the-function-fails).

Always wire `timeout` to something deliberate: a fallback message, a retry via a
second attempt, or a transfer to a live agent. An unwired `timeout` falls through to the
node's `fallback` exit, so a slow dependency and a failed one then take the same path.

<Callout>
    Each node's [reference page](/docs/workflows/reference) lists its exits, including `timeout` and
    any other non-default paths, so you can see exactly what needs wiring.
</Callout>

## No reply and repeated prompts

Voice interactions have their own failure modes:

- **Play Message** can repeat a prompt a set number of times; when the repeats
  are exhausted it takes the `repeatLimitReached` exit. Wire it to handle a
  caller who never responds — reprompt differently, or transfer out.
- Gather input into a variable and then **validate** it with a Condition before
  acting on it, rather than assuming it's well-formed.

## Unexpected values

When you branch on data — especially data from a caller or an external system —
make sure there's a path for values you didn't anticipate:

- **Switch** routes to the exit for the first matching case. Wire its `default`
  exit so an unmatched value still goes somewhere sensible.
- **Condition** always resolves to `true` or `false`; make sure both are wired
  when both outcomes need handling.

## Failures inside a loop

A **Loop Group** claims every unhandled outcome from a node inside it, whatever that outcome
is. A member whose exit is wired to nothing sends the flow back to the group, and the next
element starts. That is how the container form iterates, and it applies to a node's
`fallback` and `invalidInput` exits exactly as it applies to its `default` one.

The consequence for error handling: a body node that fails and has nothing wired does not
stop the run, and does not report anything. It silently starts the next iteration. Wire the
failure exits on nodes inside a loop body, or wrap them (see below).

To leave a loop early on purpose, put a **Loop Break** node inside the Loop Group and wire
the body to it. Running it abandons the remaining elements and sends the flow out the
group's own `break` exit. Loop Break has no fields and no onward wire of its own: wire the
group's `break` exit to wherever recovery should continue. Outside a Loop Group, Loop Break
has no loop to stop and takes `invalidInput` instead. See
[Loops](/docs/workflows/loops#leaving-a-loop-early).

## Try / Catch around a loop body

**Try / Catch** is a container. Anything inside it that takes an `invalidInput` or
`fallback` exit nothing inside handles routes to the group's `catch` exit instead of ending
the run. Turn on **Catch every unhandled exit** to catch other outcomes too, such as an
unwired `timeout`. In the catch branch, `$(sys.error.node)`, `$(sys.error.exit)`, and
`$(sys.error.message)` say what failed. A node that wires its own exit keeps it: local
handling always wins over the group.

Which group claims a failure is decided by which one is **nearer**, walking outward from the
node that failed, and a Loop Group stops that walk because it claims everything. So nesting
order decides the behavior:

- **Try / Catch inside the Loop Group** — the Try / Catch is nearer, so it catches the
  failure and the `catch` branch runs. Point `catch` at a node that continues the iteration
  and the loop carries on with the remaining elements.
- **Loop Group inside the Try / Catch** — the Loop Group is nearer and claims the failure
  first, so it starts the next iteration and the outer `catch` never runs. An outer Try /
  Catch protects the loop node itself, not the nodes in its body.

Put the Try / Catch inside the loop when you want per-element recovery. That is almost
always what you want.

<Callout type="warn">
    Don't point a Loop Group's `onFinished` or `break` exit, or a Try / Catch's `catch` exit, back
    into the group it belongs to. A `catch` inside its own try block catches its own failure again;
    a Loop Group's exits leading back into its body reopen the iteration from the first element.
    Either way the flow runs until it hits the workflow's node-execution limit. Verify reports both
    as warnings.
</Callout>

## Ending a path cleanly

Not every branch continues the conversation. Use **Disconnect** to end a voice
call deliberately (for example after an after-hours message), and **Response**
to return a result and end a flow invoked for its output. A path that just ends
with an unwired exit works, but an explicit terminal node makes the intent clear.

## Next steps

<Cards>
    <Card
        title="Loops"
        href="/docs/workflows/loops"
        description="Iterate an array, and the limits that bound it."
    />
    <Card
        title="Best Practices"
        href="/docs/workflows/best-practices"
        description="Patterns for readable, maintainable flows."
    />
    <Card
        title="Node Reference"
        href="/docs/workflows/reference"
        description="Exits for every node."
    />
</Cards>
