- Use integer return codes (`0` = success, negative = error) — check every return value from system calls and library functions.
- Use `errno` and `strerror(errno)` for POSIX/system error reporting — always check `errno` immediately after the failing call.
- Use the `goto cleanup` pattern for functions that acquire multiple resources — jump to a single cleanup section that releases resources in reverse order.
- Define project-wide error code enums — never use magic numbers for error conditions.
- Use `goto cleanup` with labeled cleanup sections to avoid deeply nested if/else error handling.
- Always set `errno = 0` before calls that may set it, and check it immediately after — `errno` is global and can be overwritten.
- Return error codes from functions and use output parameters (`int *out`) for return values — separate error signaling from data.
- Use `perror()` or `strerror(errno)` for user-facing error messages from system calls.
- Document error return values in function header comments — callers must know what each code means.