Files
libakerror/AGENTS.md
Tachikoma 651770f08f
Some checks failed
libakerror CI Build / cmake_build (push) Successful in 5m45s
libakerror CI Build / coverage (push) Successful in 2m48s
libakerror CI Build / thread_sanitizer (push) Failing after 2m57s
libakerror CI Build / mutation_test (push) Has been cancelled
Point AGENTS.md at the issue tracker for outstanding work
The agent instructions now say where new work goes -- an issue on the forge,
carrying status::grooming until its scope is settled -- and that TODO.md is the
record: why the handler ladder is a major-version change, why a copied
akerr_ErrorContext is a trap, why validating more inputs lowers branch coverage,
and why the mutation score is a floor.

The instruction that matters most here is about consumers. Three defects in this
library -- IGNORE() leaking a context, the un-namespaced coverage target, and the
missing akerrorConfigVersion.cmake -- were written down in libakstdlib's notes
and never here, so each was worked around once per consumer and nobody saw the
pattern. Filing upstream should cost a consumer one issue instead of one
workaround.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:24:49 -04:00

8.1 KiB

Repository Guidelines

Project Structure & Module Organization

This is a small C library built with CMake. Core implementation lives in src/error.c. src/lock.h is private: it selects the threading backend (pthread or none) and is not installed. The public header is generated at build time from include/akerror.tmpl.h by scripts/generrno.sh, which also generates src/errno.c under the build directory and stamps in whether the build is thread safe. CMake package and pkg-config templates are in cmake/ and akerror.pc.in. Tests are one-file C programs in tests/; shared test helpers live beside them, such as tests/err_capture.h and tests/err_threads.h.

Prose documentation lives in docs/, one file per topic — architecture.md, usage.md, status-codes.md, uncaught-errors.md, exit-status.md, thread-safety.md, building.md. README.md is deliberately short: summary, installation, quickstart, and an index into docs/. Add new documentation to the docs/ file that owns the topic and link it from the README's index rather than growing the README back.

Build, Test, and Development Commands

Use an out-of-tree build:

cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failure

cmake -S . -B build configures the build and generates akerror.h. cmake --build build compiles libakerror and test executables. ctest runs the registered unit tests. To emit CI-style results, use:

ctest --test-dir build --output-on-failure --output-junit "$(pwd)/ctest-junit.xml"

The suite includes thread tests. The run that proves there is no data race under them is ThreadSanitizer:

scripts/thread_test.sh

which configures build/tsan with -DAKERR_SANITIZE=thread, builds the library and the tests with it, and runs CTest with ASLR disabled (TSan aborts with an "unexpected memory mapping" on kernels with vm.mmap_rnd_bits above 28). AKERR_SANITIZE takes any sanitizer list, so -DAKERR_SANITIZE=address,undefined works the same way. CTest gives each test halt_on_error=1, so a report fails the test rather than being printed and passed over — running a sanitized test binary by hand does not inherit that. Set it yourself (TSAN_OPTIONS=halt_on_error=1 ./build/tsan/test_err_threads_pool) or the binary can print a race and still exit 0.

Mutation testing is available through:

cmake --build build --target mutation
scripts/mutation_test.py --target src/error.c --threshold 65

Code coverage is available through:

cmake --build build --target coverage
scripts/coverage.py --threshold 90 --branch-threshold 50

scripts/coverage.py configures its own instrumented build tree (default build/coverage, -DAKERR_COVERAGE=ON), runs the CTest suite there, and reports gcov line/branch coverage per library source. Thresholds gate each file as well as the total. Coverage measures the library's own sources only -- src/error.c, src/lock.h and the generated src/errno.c; the public header's macros expand at their call sites, so mutation testing is what checks those.

To build without threads (no locking, no thread-local storage, thread tests not registered), configure with -DAKERR_THREADS=none. auto is the default and fails the configure rather than falling back.

Coding Style & Naming Conventions

Use C99-compatible C and follow the surrounding style. Functions and types use the akerr_ prefix; macros and constants use AKERR_ or all-caps macro names such as PREPARE_ERROR. Keep generated-code changes in templates or generator scripts, not in build outputs. Preserve concise comments for invariants, macro constraints, and non-obvious error lifecycle behavior.

Locking. One recursive lock (akerr_state_lock, see src/lock.h) covers both the error pool and the status registry. A function named *_locked is called with that lock already held; a function without the suffix takes it. Never take it in a body written with the FAIL_*_RETURN macros — those return from the middle of the function and would skip the unlock. Split it instead: the locked body does the work, and a thin wrapper takes the lock, calls it, and releases it on the single return path. Do not call consumer code (akerr_log_method, akerr_handler_unhandled_error) while holding it.

Exiting. Never call exit() with a status value. Call akerr_exit(), which owns the one mapping from an akerr status to an exit code: an exit status is a byte, and every consumer status starts at 256, so passing a status through exit() silently truncates it — status 256 exits 0 and reports success. The only exits that bypass it are the two that have no status to map: ENSURE_ERROR_READY's pool-exhaustion abort in include/akerror.tmpl.h, and the NULL-context case in akerr_default_handler_unhandled_error() in src/error.c. This rule applies to test programs too, except where the test's whole point is to observe the raw truncation.

Testing Guidelines

Add tests as tests/err_<behavior>.c. Register each new test in the AKERR_TESTS list in CMakeLists.txt; tests that intentionally abort must also be listed in AKERR_WILL_FAIL_TESTS. Prefer focused executable tests that return zero on success and use existing helpers such as AKERR_CHECK. Run the CTest suite before submitting changes, and run mutation testing and coverage when changing core control-flow, reference counting, stack-trace, or handler behavior.

Tests that drive the library from several threads go in the AKERR_THREAD_SAFE branch of the AKERR_TESTS list — an -DAKERR_THREADS=none build has no threading to test — and use tests/err_threads.h, which runs a body on AKERR_TEST_THREADS threads that meet at a barrier first. Count failures per thread with AKERR_TCHECK rather than returning early: a thread that abandons its work leaves the others holding pool slots and turns one failure into a cascade. Anything the test shares between its own threads must go through __atomic builtins — a race in the test is still a race, and ThreadSanitizer cannot tell you whose it is. The capturing logger in err_capture.h is single-threaded (shared buffer, shared length); use akerr_thread_logger instead. Run scripts/thread_test.sh when changing anything that touches the pool, the registry, initialization, or the lock.

Commit & Pull Request Guidelines

Recent commits use short, imperative, sentence-case subjects, for example Fix refcount leak and stack-trace buffer overflow. Keep commits focused and describe the observable behavior changed. Pull requests should include a brief summary, tests run, any compatibility impact for public macros, generated headers, installation paths, or CMake/pkg-config consumers, and a link to the issue they close.

Agent-Specific Instructions

Outstanding work goes in the issue tracker, not in a file. Open an issue at https://source.starfort.tech/andrew/libakerror/issuestea issues create --repo andrew/libakerror — naming the file and line, the functional consequence, and what closing it would touch. Label it by kind and blast radius and leave status::grooming on it until its scope and approach are settled. Do not add outstanding items to TODO.md: that file is the record of why the handler ladder is a major-version change, why a copied akerr_ErrorContext is a trap, why validating more inputs lowers branch coverage, and why the mutation score is a floor. A description of work still to do goes stale the moment somebody does it.

This library's defects are most often found by its consumers, so make filing them cheap. Three defects in this library — IGNORE() leaking a context, the un-namespaced coverage target, and the missing akerrorConfigVersion.cmake — were written down in libakstdlib's own notes and never here, so each was worked around once per consumer and nobody saw the pattern. A consumer filing upstream should cost them one issue instead of one workaround. libakgl, libakstdlib and akbasic are all on the same forge.

Do not overwrite uncommitted user changes. Avoid editing generated files in build/; update include/akerror.tmpl.h, src/error.c, CMake files, tests, or scripts instead.