---
slug: 92-errors
title: Errores y exit codes
description: Cómo funcionan los errores en Synsema — raise, try/recover, el reporte rico --explain, la clasificación de errores, y los exit codes de run/test/check.
example_ids: [errors]
---

# Errores y exit codes

Tocá **Run** para ver un error capturado; editalo (quitá el `try`/`recover`) para ver un `Runtime error: …` sin capturar.

```synsema
-- Doc example: how errors surface — raise, try/recover, and a clean message.
-- Edit this and press Run: remove the try/recover to see an uncaught
-- "Runtime error: x must be >= 0 …" instead.
intent: "doc example: errors"

task risky(x)
    when x < 0
        raise("x must be >= 0, got " + text(x))
    give x * 2

task fails()
    give risky(-1)

-- run shows the recovery in action:
try
    print("risky(5) = " + text(risky(5)))
    print("risky(-3) = " + text(risky(-3)))      -- this one raises
recover err
    print("caught → " + err)

test "raise fails; try/recover catches the message"
    assert_eq(risky(5), 10)
    let caught be ""
    try
        let bad be risky(-1)
    recover err
        set caught to err
    assert(contains(caught, "must be >= 0"))
    assert_error(fails)
```

## Lanzar y capturar

```synsema
raise("mensaje")            -- fallar a propósito (o re-propagar dentro de recover)

try
    risky()
recover err
    log "falló: " + err     -- err es el mensaje (text)
    raise(err)              -- RE-PROPAGA; sin esto, recover se traga el error
```

`give` y `stop` **no** son errores — pasan por `try/recover`. (`fail(...)` arma una respuesta HTTP, no lanza.) Testeá que algo lanza con `assert_error(task)`. La forma sentencia `raise "msg"` / `raise err` (sin paréntesis) también funciona, y un `raise` solo es error de parseo con el fix en el mensaje. ⚠️ En el v0.5.1 publicado y anteriores, `raise "msg"` sin paréntesis eran dos expresiones inertes — no-op silencioso; en esos binarios siempre `raise("msg")`.

Otro error que conviene reconocer a primera vista: `'decide' is a reserved word in Synsema; choose another name for the member after '.'` (en v0.5.1 y anteriores: el críptico `Expected IDENTIFIER, got DECIDE`) significa que una **palabra reservada dura** se usó como nombre de export, parámetro o miembro — `mod.decide(...)`, `task wait(reason)`. Las palabras LLM `reason`/`decide`/`analyze`/`generate` están reservadas en todas partes; renombrá (`resolve`, `why`).

## Exit codes

| Exit | Cuándo |
|---|---|
| `0` | éxito |
| `1` | error de parseo, error de runtime, o algún **agente** spawneado terminó en `ERROR` |
| `2` | error de uso — un argumento faltante, `synsema openapi` sin `serve`, o (v0.6.14+) un **`--flag` desconocido** (rechazado, no ignorado, en `run`/`test`/`conform`) |

El `run` plano imprime una línea estable: `Runtime error: <file>:<line>:<col>: <msg>`. ¿Lo medís en una shell? Redirigí (`… >/dev/null; echo $?`) — no pipees antes de `echo $?`, o leés el code del pipe.

## Diagnósticos ricos (`--explain`)

```sh
synsema run --explain program.syn                # legible, en stderr
synsema run --explain --format json program.syn  # estructurado, para tools/agentes
```

El reporte agrega: contexto de fuente, call stack, variables visibles, el intent del programa, una **clasificación** (`data` / `io` / `logic` / `capability` / `type`), si es recuperable, y sugerencias de fix. El exit code no cambia en ningún caso.

## Formas comunes de error

- **Capacidad:** `Capability not granted: net("…")` — agregá el `require` correspondiente (o ampliá el scope). Si dice *declared but above the host ceiling*, el `require` ya está: solo el host (`--sandbox`/`--cap-set`/el techo del embebedor) puede permitirlo. `Capability not granted: file_read("…")` desde un `render` significa que no se declaró la lectura de un template en **disco** — agregá `require file.read("…")` (o empaquetá el template con `synsema build`).
- **Perfil pure:** `<name>: not available in the pure profile — this run has no filesystem` (e igual sin procesos hijo / sockets / drivers de base de datos / threads de scheduler) — el builtin quedó amurallado por `--profile pure`; quitá el flag para correrlo en nativo. Ver [Sandboxing](22-sandbox).
- **`run_program`:** `above parent ceiling` / `above parent profile` en el audit del hijo (un hijo pidió más de lo que presta el padre — recortado, no fatal); `run_program: timed out after Ns` (`timed_out: true`); `run_program: max depth N exceeded`; `run_program: env "X" is a secret — reveal() it explicitly`.
- **`synsema build`:** `bundle corrupt (sha256 mismatch)` (un binario construido manipulado se niega a correr); `"<path>" is part of the bundle (read-only)` (una escritura a un asset empaquetado); `this is a built program (synsema build); rebuild it…` (`--engine update` sobre un binario construido); en tiempo de build, `a `use` with a dynamic path cannot be bundled` y `'…' escapes the bundle root`.
- **Tipo:** una operación recibió el tipo equivocado (p. ej. `as_secret(123)` → "expects text or bytes").
- **Datos:** una key de map faltante, un índice fuera de rango, JSON inválido en `json_decode`.
