# Step 5 — Failure Handling ## What this step delivers `latprobe` now handles all three real-world failure modes explicitly: | Failure | Exit code | Behaviour | |---------|-----------|-----------| | DNS resolution failure | 2 | Partial timing (DNS duration) shown; classified as `dns` | | Connection failure (refused / unreachable) | 3 | Partial timing shown; classified as `connect` | | Timeout (exceeded `--timeout`) | 4 | Partial timing shown; classified as `timeout` | | TLS handshake failure | 5 | Partial timing shown; classified as `tls` | | HTTP status ≥ 400 (with `--fail`) | 6 | Full timing shown, status annotated with ✗ | | HTTP status ≥ 400 (without `--fail`) | 0 | Full timing shown, status visible but exit is 0 | With `-n` sampling, network failures do **not** abort the run — remaining samples continue and successful ones are aggregated normally. The failure count and cause are reported alongside the aggregate. When multiple URLs or failure types occur, the process exits with the **highest** exit code encountered. ## New flags | Flag | Default | Description | |------|---------|-------------| | `--timeout` | `10s` | Per-request timeout (e.g. `500ms`, `2s`, `30s`) | | `--fail` | off | Exit with code 6 when any URL returns HTTP status ≥ 400 | ## Examples ### DNS failure ```sh $ ./latprobe https://nonexistent.invalid; echo $? https://nonexistent.invalid (FAILED) ✗ dns: dial tcp: lookup nonexistent.invalid: no such host 2 ``` ### Connection refused ```sh $ ./latprobe http://localhost:1; echo $? http://localhost:1 (FAILED) ✗ connect: dial tcp [::1]:1: connect: connection refused 3 ``` ### Timeout ```sh $ ./latprobe --timeout 1s https://example.com:81; echo $? https://example.com:81 (FAILED) ✗ timeout: context deadline exceeded 4 ``` ### HTTP error — default (exit 0) ```sh $ ./latprobe https://httpbin.org/status/500; echo $? https://httpbin.org/status/500 (500) DNS lookup : 27.75 ms TCP connect : 114.47 ms TLS handshake : 245.59 ms Server (TTFB) : 113.06 ms Transfer : 0.21 ms ───────────────────────────── Total : 502.05 ms 0 ``` ### HTTP error — with `--fail` (exit 6) ```sh $ ./latprobe --fail https://httpbin.org/status/404; echo $? https://httpbin.org/status/404 (404 ✗) DNS lookup : 3.35 ms ... Total : 476.01 ms 6 ``` ### Mixed sampling (some network failures) ```sh $ ./latprobe -n 5 https://flaky-host.example.com; echo $? https://flaky-host.example.com (200, 3 samples, 2 failed) min avg max DNS lookup : 5.00 ms 5.10 ms 5.20 ms ... ───────────────────────────────────────────────── Total : 80.00 ms 85.00 ms 92.00 ms ✗ 2 × connect: connection refused 3 ``` ### JSON output with failure ```sh $ ./latprobe --json https://nonexistent.invalid | jq . [ { "url": "https://nonexistent.invalid", "status": 0, "succeeded": 0, "failed": 1, "errors": [ { "phase": "dns", "count": 1, "message": "dial tcp: lookup nonexistent.invalid: no such host" } ] } ] ``` ## JSON schema changes `jsonEntry` now includes: ```json { "url": "https://example.com", "status": 200, "succeeded": 3, "failed": 2, "phases": { ... }, "errors": [ { "phase": "connect", "count": 2, "message": "connection refused" } ] } ``` - `phases` is omitted when `succeeded == 0` - `errors` is omitted when `failed == 0` - `status` is 0 when no sample reached a response ## Exit code reference | Code | Meaning | |------|---------| | 0 | All probes succeeded | | 1 | Usage error (no URLs, bad flags) | | 2 | DNS resolution failure | | 3 | Connection failure | | 4 | Timeout | | 5 | TLS handshake failure | | 6 | HTTP status ≥ 400 (only with `--fail`) |