errors
Error Handling skill for the ikigai project
When & Why to Use This Skill
This Claude skill provides a comprehensive error handling framework for C-based software development, specifically tailored for the ikigai project. It establishes a rigorous decision framework using three distinct mechanisms—res_t for recoverable errors, assertions for contract validation, and PANIC for unrecoverable states. By standardizing return patterns, error codes, and critical memory lifetime rules (using talloc), it enables the creation of robust, memory-safe, and maintainable systems while preventing common pitfalls like use-after-free bugs during error propagation.
Use Cases
- Scenario 1: Implementing robust IO, configuration loading, or protocol parsing by utilizing the res_t type to ensure graceful error propagation without system crashes.
- Scenario 2: Enforcing internal API contracts and preconditions using assertions that provide active debugging triggers in development while being optimized out in release builds.
- Scenario 3: Managing critical system failures such as Out-of-Memory (OOM) or internal state corruption using the PANIC mechanism to ensure immediate, safe termination.
- Scenario 4: Preventing complex memory vulnerabilities by applying 'Error Context Lifetime' rules and talloc_steal patterns when returning errors from nested function calls.
- Scenario 5: Standardizing error reporting and handling across a large codebase using predefined error codes and macros like TRY() and CHECK() for cleaner, more readable code.
| name | errors |
|---|---|
| description | Error Handling skill for the ikigai project |
Error Handling
Three Mechanisms
| Mechanism | When | Compiles Out? |
|---|---|---|
res_t |
IO, parsing, external failures | No |
assert() |
Preconditions, contracts | Yes (-DNDEBUG) |
PANIC() |
OOM, corruption, impossible states | No |
Decision Framework
- OOM? →
PANIC() - Can happen with correct code? →
res_t - Precondition/contract? →
assert() - Impossible state? →
PANIC()
Return Patterns
- res_t - Failable operations (IO, parsing)
- Direct pointer - Simple creation (PANICs on OOM)
- void - Cannot fail
- Primitive - Queries (size, bool checks)
- Raw pointer - Into buffer (use
_ptrsuffix) - Callback - res_t for events, value for queries, void for side-effects
Core Types
typedef struct {
union { void *ok; err_t *err; };
bool is_err;
} res_t;
typedef struct err {
err_code_t code;
const char *file;
int32_t line;
char msg[256];
} err_t;
Error Codes
| Code | Value | Usage |
|---|---|---|
OK |
0 | Success/no error |
ERR_INVALID_ARG |
1 | Invalid argument validation |
ERR_OUT_OF_RANGE |
2 | Out of range values |
ERR_IO |
3 | File operations, config loading |
ERR_PARSE |
4 | JSON/protocol parsing |
ERR_DB_CONNECT |
5 | Database connection failures |
ERR_DB_MIGRATE |
6 | Database migration failures |
ERR_OUT_OF_MEMORY |
7 | Memory allocation failures |
ERR_AGENT_NOT_FOUND |
8 | Agent not found in array |
ERR_PROVIDER |
9 | Provider error |
ERR_MISSING_CREDENTIALS |
10 | Missing credentials |
ERR_NOT_IMPLEMENTED |
11 | Not implemented |
Macros
OK(value)/ERR(ctx, CODE, "msg", ...)- Create resultsTRY(expr)- Extract value or return errorCHECK(res)- Propagate error if failedis_ok(&res)/is_err(&res)- Inspect
Assertions
Build behavior:
debugbuild (default): asserts are ACTIVE → violation triggersSIGABRTreleasebuild (-DNDEBUG): asserts are COMPILED OUT → violation causes undefined behavior (segfault, corruption, etc.)
We normally run debug builds. When you see assert(x) fail, expect SIGABRT, not a segfault.
Guidelines:
- Mark with
// LCOV_EXCL_BR_LINE - Test both paths (pass + SIGABRT)
- Split compound assertions
- Assert side-effect free
PANIC Usage
// OOM - most common
if (ptr == NULL) PANIC("Out of memory"); // LCOV_EXCL_BR_LINE
// Switch default
default: PANIC("Invalid state"); // LCOV_EXCL_LINE
// Corruption
if (size > capacity) PANIC("Array corruption"); // LCOV_EXCL_LINE
Trust Boundary
- User input → validate exhaustively with
res_t, never crash - Internal functions →
assert()preconditions, trust caller validated
Testing
- Assertions:
#ifndef NDEBUG+tcase_add_test_raise_signal(tc, test, SIGABRT) - PANIC:
tcase_add_test_raise_signal(tc, test, SIGABRT)(all builds) - OOM: Cannot be tested (process terminates)
Coverage Exclusions
| Code | Marker |
|---|---|
| Assertions | // LCOV_EXCL_BR_LINE |
| OOM checks | // LCOV_EXCL_BR_LINE |
| PANIC logic errors | // LCOV_EXCL_LINE |
New exclusions require updating LCOV_EXCL_COVERAGE in Makefile.
Error Context Lifetime (Critical Rule)
THE TRAP: Errors allocated on a context that gets freed become use-after-free bugs.
// BROKEN - error is allocated on foo, then foo is freed
res_t ik_foo_init(void *parent, foo_t **out) {
foo_t *foo = talloc_zero_(parent, sizeof(foo_t));
res_t result = ik_bar_init(foo, &foo->bar); // Error allocated on foo
if (is_err(&result)) {
talloc_free(foo); // FREES THE ERROR TOO!
return result; // USE-AFTER-FREE CRASH
}
}
THE FIX: Error allocation context must survive the error's return. Either:
Option A (Preferred): Pass parent to sub-functions for error allocation:
res_t ik_foo_init(void *parent, foo_t **out) {
bar_t *bar = NULL;
res_t result = ik_bar_init(parent, &bar); // Error on parent - survives!
if (is_err(&result)) return result;
foo_t *foo = talloc_zero_(parent, sizeof(foo_t));
talloc_steal(foo, bar);
foo->bar = bar;
*out = foo;
}
Option B (Band-aid): Reparent error before freeing context:
res_t ik_foo_init(void *parent, foo_t **out) {
foo_t *foo = talloc_zero_(parent, sizeof(foo_t));
res_t result = ik_bar_init(foo, &foo->bar);
if (is_err(&result)) {
talloc_steal(parent, result.err); // Save error to survivor
talloc_free(foo);
return result;
}
}
Rule: When returning an error after freeing a context:
- The error must be allocated on a context that survives the free
- Prefer Option A (pass parent for error allocation)
- Use Option B (talloc_steal) as fallback
Reference: See fix.md and project/error_handling.md#error-context-lifetime-critical for detailed analysis.
References
Full details: project/return_values.md, project/error_handling.md, project/error_patterns.md, project/error_testing.md