Files
http-latency-prober/docs/explanations/2026-07-02-13-25-hxprobe-worst-exit-code-and-render-loop.md
Jan Novak f487a4b1bd docs: hxprobe plans, summaries, explanations, usage, and changelog
Plans (docs/plans/):
- 2026-07-01-23-47-py-hxprobe-httpx.md — initial httpx probe design
- 2026-07-02-09-32 through 14-05 — standalone project, toolchain, usage
  doc + Makefile, file input (-f), simplification pass, run-summary footer

Summaries (docs/summaries/): one per completed feature, recording what
was actually built, deviations from the plan, and verification steps

Explanations (docs/explanations/): two deep-dives written during review —
hxprobe concurrency model and worst-exit-code + render-loop analysis

Usage (docs/usage/hxprobe.md): overview with pointer to hxprobe/USAGE.md
for the full runnable reference

Walkthrough (docs/py-latprobe-walkthrough.md): narrative tour of the
latprobe Python package for interview / code-review context

CHANGELOG.md: entries for all hxprobe features (toolchain, usage doc,
file input, simplification, run-summary footer)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 13:22:59 +02:00

6.0 KiB

What does the per-URL worst-exit-code / render loop do, and does it make sense?

Question

Selection in hxprobe/hxprobe/cli.py:454-472:

for i, (url, (succeeded, failed)) in enumerate(zip(urls, all_results)):
    for r in failed:
        c = _phase_code(r.fail_phase)
        if c > worst:
            worst = c
    if ns.fail:
        for r in succeeded:
            if r.status_code >= 400:
                worst = max(worst, EXIT_HTTP)

    last_detail = succeeded[-1].detail if succeeded else (failed[-1].detail if failed else None)

    if ns.json_out:
        json_items.append(_build_json_entry(url, succeeded, failed, last_detail))
        continue

    if i > 0:
        stdout.write("\n")
    _print_url(url, succeeded, failed, count, ns.fail, stdout)

explain following block and if it makes sense, other related stuff, and save it in explanations

What it does

This is the single pass over per-URL results that runs after the thread pool (cli.py:448-449, see 2026-07-02-13-10-hxprobe-concurrency.md) finishes. It does two jobs in one loop: compute the process's final exit code, and render output (text or accumulate JSON) — one URL at a time, in input order (guaranteed by zip(urls, all_results) since ex.map preserves order).

1. Network-failure exit code (cli.py:455-458)

for r in failed:
    c = _phase_code(r.fail_phase)
    if c > worst:
        worst = c

Every failed sample (across every URL, since worst is declared once before the loop) is mapped to an exit code via _phase_code / _PHASE_EXIT (cli.py:24-32):

_PHASE_EXIT: dict[str, int] = {
    "dns": EXIT_DNS,        # 2
    "timeout": EXIT_TIMEOUT, # 4
    "tls": EXIT_TLS,         # 5
}
def _phase_code(fail_phase: str) -> int:
    return _PHASE_EXIT.get(fail_phase, EXIT_CONNECT)  # 3, the fallback

Any fail_phase not in the table — "connect", "transfer", "request" — falls back to EXIT_CONNECT (3). worst tracks the running max across all URLs/samples, so the process exit code always reflects the single worst failure class seen, per the exit-code table (0 ok … 6 http via --fail).

2. --fail (HTTP status ≥ 400) exit code (cli.py:459-462)

if ns.fail:
    for r in succeeded:
        if r.status_code >= 400:
            worst = max(worst, EXIT_HTTP)

Only runs when --fail is passed. Note this scans succeeded — a 404 is not a network failure, so those Results land in succeeded with a populated status_code; --fail is what turns "successfully got a bad status" into a non-zero exit, curl-style.

3. Verbose-detail selection (cli.py:464)

last_detail = succeeded[-1].detail if succeeded else (failed[-1].detail if failed else None)

Prefers the last successful sample's detail (freshest full picture: IP, protocol, TLS, headers); falls back to the last failed sample's detail if nothing succeeded (e.g. resolved IP is still known even on a connection refusal); None if there's nothing to show. Reasonable design — surfaces partial diagnostic info even on total failure.

4. JSON accumulation vs. text rendering (cli.py:466-472)

if ns.json_out:
    json_items.append(_build_json_entry(url, succeeded, failed, last_detail))
    continue

if i > 0:
    stdout.write("\n")
_print_url(url, succeeded, failed, count, ns.fail, stdout)

JSON mode builds up json_items (dumped once after the loop) and skips straight to the next URL via continue. Text mode writes a blank-line separator before every URL block except the first (i > 0), then delegates actual formatting to _print_url, which picks one of four branches (single / aggregate / all-failed / mixed) based on n_ok/n_fail/total_count.

Does it make sense?

Yes, structurally. Combining exit-code accumulation and rendering into one O(n) pass is reasonable for a CLI at this scale — no need to split into two loops. The last_detail fallback logic is a genuinely good touch. The one stylistic wrinkle — the DNS/connect/timeout/tls loop uses if c > worst: worst = c while the --fail branch uses worst = max(worst, EXIT_HTTP) for the same "keep the running max" purpose — is harmless inconsistency, not a bug.

Update (2026-07-02): the "does a single worst-code exit even make sense across multiple URLs with different errors" question came back as a follow-up and led to a real feature — see 2026-07-02-14-05-hxprobe-run-summary-footer.md. Short version: the scalar exit code is kept (it's a documented cross- implementation contract with latprobe/Go), but multi-URL runs now get an end-of-run summary footer tallying every URL's outcome, so the "worst code" is no longer the only visibility into what happened. That change also unified the two idioms noted above into one max(...) call.

One real gap, found while checking this: -n/--count is unvalidated. cli.py:349-355 declares --count as type=int, default=1 with no minimum. _run_samples (cli.py:64-69) does for _ in range(count): ..., so --count 0 (or any negative value) makes the loop body never execute, and both succeeded and failed come back empty for that URL. Confirmed live:

$ hxprobe --count 0 https://example.com
  (0, 0 samples)
exit=0

$ hxprobe --count -2 https://example.com
  (0, 0 samples)
exit=0

The URL itself is missing from the header, status reads 0, and the exit code is 0 (success) — because _print_url falls into the elif n_fail == 0 aggregate branch with summarize([]), which returns a bare Aggregate() (all defaults, url="") rather than anything referencing the actual url variable. This is silent garbage output instead of a clear usage error, and it's inconsistent with how the rest of run() already validates arguments (e.g. the parser.error(...) calls for the urls/--file mutual-exclusion checks at cli.py:407-410). Worth a parser.error("count must be >= 1")-style guard if this is ever picked up — not fixed here since it wasn't asked for, just flagged as a finding.