---
title: Handle errors by code
description: "Read a failure's three channels, branch on the stable code name, and switch policy on the coarse status, in every language."
---

Your program needs to react to a runtime failure without parsing prose. Every ClikaRT failure carries the same three channels, in every language: a **coarse status** (the policy switch: retry, reject, fail), a **human message** (diagnostics for a log line), and a **stable code name** (the fine-grained machine channel, e.g. `TIMED_OUT`, `BACKEND_NOT_LOADED`). The rule this page exists for: branch on the code name, never on the message text. A failure you can act on (a bad shape, an option out of range, a missing file) reads as plain text in every build and names the operation and the values it refused. A message of the form `E<digits>` means a fault inside the runtime: its value is specific to the build that produced it, so it is nothing to record, compare or branch on; report it verbatim with the runtime version. The code name is machine-readable in every build.

## Read the three channels

The program provokes one failure (a shape-mismatched matmul) and reads everything the error carries. 

<LangTabs>

<LangTab value="cpp">

```cpp title="three_channels.cpp"
#include <cstdio>

#include <ClikaRT/clika_rt.h>

using ClikaRT::DataType;
using ClikaRT::Error;
using ClikaRT::Status;
using ClikaRT::Tensor;
namespace ops = ClikaRT::ops;

int main() {
    const Tensor a   = Tensor::ones({3, 4}, DataType::Float32);
    const Tensor bad = Tensor::ones({5, 5}, DataType::Float32);

    try {
        const Tensor y = ops::matmul(a, bad);
    } catch (const Error& e) {
        // The three channels of every runtime failure.
        std::printf("status:    %d\n", static_cast<int>(e.status()));
        std::printf("message:   %s\n", e.what());
        std::printf("code name: %s\n", e.code_name().c_str());

        // Branch on the code NAME: stable in every build, and the only
        // one of the three to compare against. A failure you can act on,
        // like this one, states the shapes in plain text in every build; a
        // message of the form E<digits> means a defect inside the runtime,
        // and its value is specific to the build that produced it.
        if (e.code_name() == "INVALID_ARGUMENT")
            std::printf("-> reject this request, keep serving\n");

        // The coarse status is the POLICY switch.
        switch (e.status()) {
            case Status::Unavailable:     /* shed under load: retry later */ break;
            case Status::InvalidArgument: /* caller bug: answer 400 */ break;
            default:                      /* fail loudly */ break;
        }
    }
    return 0;
}
```

</LangTab>

<LangTab value="python">

```python title="three_channels.py"
import numpy as np

import clika_runtime as crt


def code_name_of(err: BaseException) -> str:
    """The machine-readable code name a runtime error carries ('' if none):
    every ClikaRT exception exposes it as ``code_name``."""
    return getattr(err, "code_name", "")


def main() -> None:
    a = crt.tensor(np.ones((3, 4), dtype=np.float32))
    bad = crt.tensor(np.ones((5, 5), dtype=np.float32))

    try:
        a @ bad
    except RuntimeError as e:
        # Python raises a typed exception per status: str(e) is the message
        # alone, and the stable code name rides the exception's code_name.
        print(f"message:   {e}")
        print(f"code name: {code_name_of(e)}")

        # Branch on the code NAME: stable in every build, and the only
        # one of the three to compare against. A failure you can act on,
        # like this one, states the shapes in plain text in every build; a
        # message of the form E<digits> means a defect inside the runtime,
        # and its value is specific to the build that produced it.
        if code_name_of(e) == "INVALID_ARGUMENT":
            print("-> reject this request, keep serving")


if __name__ == "__main__":
    main()
```

</LangTab>

</LangTabs>

```text
status:    1
message:   ops::matmul: cannot contract a [3, 4] with b [5, 5]: a's last dim (4) must equal b's dim 0 (5)
code name: INVALID_ARGUMENT
-> reject this request, keep serving
```

Status `1` is `InvalidArgument`, the value of the C++ `Status` enum, whose numbering is append-only. The message names the operation and the two shapes it refused, in a release build as in a debug build. Python prints the status by name rather than by number (`status:    InvalidArgument`) and is otherwise identical: the exception carries `.status`, its text and `.code_name`, and its class is the status, so a caller can switch on the type instead of reading the name.

## Branch on the code name, never the message

The message exists for a human reading a log. An argument mistake reads as a sentence that names the operation and the values, in every build, but its wording can change between versions, so it is not a contract. A message of the form `E<digits>` is a runtime fault code: report it verbatim together with the runtime version; it is not an identifier to record, compare or branch on. The code name is the contract: a stable, append-only name for the fine-grained status the failure originated with, readable in every build. An empty code name is meaningful too: the failure did not originate inside the runtime (the C++ `Error` from your own code, a client-side load failure), so there is nothing machine-readable to branch on.

## A placement failure names its reason

One message with a fixed shape: placing work on a device whose backend did not come up fails with the reason spelled out, `cannot allocate on device <name>: <why>`. The `<why>` names what actually happened: the backend library is not shipped beside the runtime, it failed to load (with the loader's cause), or it loaded and brought up no device. The code name stays the branch point; the message is now worth logging as is. On a machine without the Metal backend:

```cpp title="backend_reason.cpp"
#include <cstdio>

#include <ClikaRT/clika_rt.h>

using ClikaRT::DataType;
using ClikaRT::Device;
using ClikaRT::Error;
using ClikaRT::Tensor;

int main() {
    try {
        const Tensor t = Tensor::ones({2, 2}, DataType::Float32, Device::metal());
        std::printf("placed: %s\n", t.to_string().c_str());
    } catch (const Error& e) {
        std::printf("status:    %d\n", static_cast<int>(e.status()));
        std::printf("message:   %s\n", e.what());
        std::printf("code name: %s\n", e.code_name().c_str());
    }
    return 0;
}
```

```text
status:    4
message:   cannot allocate on device Metal:0: The metal backend could not load: not shipped in this install. The metal backend is not part of this ClikaRT build.
code name: BACKEND_NOT_LOADED
```

The same reason text reaches every binding through its language's error channel, and `clikart-cli` prints it when `--device` names a backend that cannot come up.

{/* CERTIFICATION: both captures are real output of the program shown, run
under the pinned release's clika_runtime cp313 manylinux wheel (sha256 checked
against the digest published with it) in a fresh venv on linux x86_64, with no
credential in the environment and no per-user license file present. The refusal texts are the
runtime's own and are quoted unchanged. */}

## A licensing refusal carries `LICENSE_FAILED`

ClikaRT runs compute under a license ([License the runtime](license-the-runtime.mdx) is where the credential goes). A call refused for licensing carries the code name `LICENSE_FAILED`, or `LICENSE_EXPIRED` for a credential past its end date, and the runtime writes one line to the console naming the refusal. On a process started with no credential at all:

```text
[ClikaRT] [error] clikart license invalid — no ClikaRT license is configured for this process
```

```python
import numpy as np
import clika_runtime as crt

t = crt.tensor(np.ones((2, 3), dtype=np.float32))
try:
    (t + t).numpy()
except crt.ClikaRTError as e:
    print(type(e).__name__, e.status, e.code_name, sep=" | ")
    print(e)
```

```text
InternalError | Internal | LICENSE_FAILED
no ClikaRT license is configured for this process — a license key comes from the Licenses page of your project on the CLIKA platform; set CLIKA_RT_LICENSE to the key or to a file holding it, or install it with the clikart-license-init tool
```

The message names where the credential comes from and both places it can go, so it is worth logging as is. Licensing is where this page's rule earns its keep: the coarse status reads `Internal`, whose row in the table below sends a reader to file a report, and an unset variable is not something to file. Branch on `LICENSE_FAILED` before you consult the status.

Making a tensor is not an operator, so a tensor still builds without a credential; the refusal arrives at the first operator. A process that has to survive a missing credential checks for one before it starts computing, or catches the refusal at its first operator.

## Switch policy on the coarse status

The code name says what happened; the coarse status says what KIND of thing happened, which is usually all a policy needs:

| Status | Meaning | Usual reaction |
| --- | --- | --- |
| `InvalidArgument` | the caller's request cannot be right | reject it (a 400 in [the serving guide](serve-over-http.mdx)) |
| `NotFound` | a named thing does not exist | reject or fall back (a 404) |
| `Unsupported` | this build or backend cannot do it | fail fast at startup, not per request |
| `Internal` | the runtime's own invariant broke | log everything, file it |
| `Unavailable` | transient: the work was shed under load | retry later (a 503) |

## Without exceptions

C++ callers that would rather not pay for exceptions read the same three channels off a `Result<T>`: `ok()`, `status()`, `message()`, `code_name()`. `CLIKART_TRY(...)` captures a throwing call as a `Result` where a failure is an expected outcome; [the serving guide](serve-over-http.mdx) uses it for exactly that on its 400 paths.

The serving-side `nn::KVCache` is a worked example of the `Result` surface: its per-layer accessors (`keys_impl`, `values_impl`, `conv_state_impl`, `recurrent_state_impl`) return a failed `Result` for a layer index outside `[0, num_layers())` instead of crashing, and `num_layers()` reports the count without allocating:

```cpp title="kv_bounds.cpp"
#include <cstdio>

#include <ClikaRT/clika_rt.h>

using ClikaRT::Result;
using ClikaRT::Tensor;
namespace nn = ClikaRT::nn;

int main() {
    // The smallest real cache: two full-attention layers, one slot.
    nn::KVCacheConfig config;
    config.num_layers         = 2;
    config.num_kv_heads       = 1;
    config.head_dim           = 4;
    config.max_seqs           = 1;
    config.max_tokens_per_seq = 8;
    const nn::KVLayerSpec specs[2] = {};  // AttentionKV rows by default

    const nn::KVCache cache = nn::KVCache::make_impl(config, specs).value_or_throw();

    // The per-layer accessors return a failed Result for a layer outside
    // [0, num_layers()); no exception, no crash.
    std::printf("layers: %d\n", cache.num_layers());
    const Result<Tensor> in_range  = cache.keys_impl(1);
    const Result<Tensor> out_range = cache.keys_impl(2);
    std::printf("keys(1): ok=%s\n", in_range.ok() ? "true" : "false");
    std::printf("keys(2): ok=%s, message: %s, code name: %s\n",
                out_range.ok() ? "true" : "false",
                out_range.message().c_str(), out_range.code_name().c_str());
    return 0;
}
```

```text
layers: 2
keys(1): ok=true
keys(2): ok=false, message: kv cache layer 2 is out of range: the cache has 2 layer(s), code name: INVALID_ARGUMENT
```

[Tutorial part 1](../getting-started/first-program/01-your-first-program.mdx) introduces the error model this page operationalizes; the python examples' errors chapter walks the same contract with an oracle per claim.
