Handle errors by code
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. The C samples abbreviate the api-table bootstrap that tutorial part 1 shows in full.
- C++
- C
- Python
- Kotlin
- Go
- Rust
#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;
}
#include <stdio.h>
#include <string.h>
#include "clika_rt/clika_rt_core.h"
/* api: the handshaken table from tutorial part 1 (dlopen + clika_rt_get_api). */
extern const clika_rt_api* api;
int main(void) {
const clika_rt_stream_or_device dflt = {CLIKA_RT_STREAM_OR_DEVICE_DEFAULT};
clika_rt_tensor* a = NULL;
clika_rt_tensor* bad = NULL;
api->tensor_ones((const int64_t[]){3, 4}, 2, CLIKA_RT_DATA_TYPE_FLOAT32,
dflt, dflt, &a);
api->tensor_ones((const int64_t[]){5, 5}, 2, CLIKA_RT_DATA_TYPE_FLOAT32,
dflt, dflt, &bad);
/* A fallible member returns an error object; NULL means success, and on
* failure the handle-out stays NULL. Both operands were donated to the op. */
clika_rt_tensor* y = NULL;
clika_rt_error* e = api->op_matmul(a, bad, NULL, 0, CLIKA_RT_ACTIVATION_IDENTITY,
0, 0, 0.0, 0.0, &y);
if (e != NULL) {
char msg[512], code[128];
size_t mlen = sizeof msg, clen = sizeof code;
api->error_message(e, msg, &mlen);
api->error_code_name(e, code, &clen);
/* The three channels of every runtime failure. */
printf("status: %d\n", (int)api->error_status(e));
printf("message: %s\n", msg);
printf("code name: %s\n", code);
/* 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 (strcmp(code, "INVALID_ARGUMENT") == 0)
printf("-> reject this request, keep serving\n");
/* The coarse status is the POLICY switch. */
switch (api->error_status(e)) {
case CLIKA_RT_STATUS_UNAVAILABLE: /* shed under load: retry later */ break;
case CLIKA_RT_STATUS_INVALID_ARGUMENT: /* caller bug: answer 400 */ break;
default: /* fail loudly */ break;
}
api->error_free(e); /* errors are single-owner: free exactly once */
}
return 0;
}
import numpy as np
import clika_runtime as crt
CODE_MARKER = "[code: "
def code_name_of(err: RuntimeError) -> str:
"""The machine-readable code name a runtime error carries ('' if none)."""
msg = str(err)
if CODE_MARKER not in msg:
return ""
return msg.rsplit(CODE_MARKER, 1)[1].rstrip("]")
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 folds the channels into RuntimeError: str(e) is the message,
# with the stable code name as its bracketed [code: NAME] suffix.
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()
import io.clika.runtime.ClikaRt
import io.clika.runtime.ClikaRtException
import io.clika.runtime.Tensors
import io.clika.runtime.matmul
fun main() {
ClikaRt.load()
val a = Tensors.ones(longArrayOf(3, 4))
val bad = Tensors.ones(longArrayOf(5, 5))
try {
a matmul bad
} catch (e: ClikaRtException) {
// The three channels of every runtime failure.
println("status: ${e.status}")
println("message: ${e.message}")
println("code name: ${e.codeName}")
// 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.codeName == "INVALID_ARGUMENT")
println("-> reject this request, keep serving")
}
}
package main
import (
"errors"
"fmt"
"log"
"github.com/Clika/clika_runtime/bindings/go/clikart"
)
func must[T any](v T, err error) T {
if err != nil {
log.Fatal(err)
}
return v
}
func main() {
api := must(clikart.Load("libClikaRT.so"))
a := must(api.TensorOnes([]int64{3, 4}, clikart.Float32,
clikart.StreamOrDevice{}, clikart.StreamOrDevice{}))
bad := must(api.TensorOnes([]int64{5, 5}, clikart.Float32,
clikart.StreamOrDevice{}, clikart.StreamOrDevice{}))
if _, err := clikart.Matmul(a, bad); err != nil {
var e *clikart.Error
if !errors.As(err, &e) {
log.Fatal(err) // not a runtime failure
}
// The three channels of every runtime failure.
fmt.Printf("status: %d\n", e.Status)
fmt.Printf("message: %s\n", e.Message)
fmt.Printf("code name: %s\n", e.CodeName)
// 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.CodeName == "INVALID_ARGUMENT" {
fmt.Println("-> reject this request, keep serving")
}
}
}
use clika_rt::{sys, Api};
const F32: sys::clika_rt_data_type = sys::clika_rt_data_type_CLIKA_RT_DATA_TYPE_FLOAT32;
const IDENTITY: sys::clika_rt_activation = sys::clika_rt_activation_CLIKA_RT_ACTIVATION_IDENTITY;
fn main() -> clika_rt::Result<()> {
let api = Api::load("libClikaRT.so")?;
let a = api.tensor_full(&[3, 4], 1.0, F32)?;
let bad = api.tensor_full(&[5, 5], 1.0, F32)?;
// The f_* forms return Result; both tensor operands are CONSUMED (the
// donation law), and the absent() slot leaves the optional bias empty.
if let Err(e) = api.f_op_matmul(a, bad, api.absent(), false, IDENTITY,
false, false, 0.0, 0.0) {
// The three channels of every runtime failure.
println!("status: {}", e.status);
println!("message: {}", e.message);
println!("code name: {}", e.code_name);
// 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" {
println!("-> reject this request, keep serving");
}
}
Ok(())
}
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 same value in the C++ Status enum and the C clika_rt_status (the C surface freezes its numbering append-only). The message names the operation and the two shapes it refused, in a release build as in a debug build. Python is the one variation: it folds all three channels into RuntimeError, the message carrying the code name as a bracketed [code: NAME] suffix; the twelve-line helper above is the whole extraction.
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 an internal fault code: it stands for a defect inside the runtime, its value is specific to the build that produced it (it changes between releases), and it is not an identifier to record, compare or branch on. Report it verbatim together with the runtime version; it decodes on the release side. The code name is the contract: a stable, append-only name for the fine-grained status the failure originated with, readable in every build flavor. 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:
#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;
}
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 clika-modelverse prints it when --device names a backend that cannot come up.
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) |
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) |
The C surface adds one protocol-level status of its own, BUFFER_TOO_SMALL, which is not a failure at all: it drives the sized-string two-call protocol every C string read uses.
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 uses it for exactly that on its 400 paths. The C samples never throw by construction: every fallible api-table member returns the error object directly.
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:
#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;
}
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 introduces the error model this page operationalizes; the python examples' errors chapter walks the same contract with an oracle per claim.