Add websockets to the dev extra so the repro harness smoke test can import
its websocket client dependency in the CI test matrix. Also apply ruff
formatting to the files the formatter check was rejecting so the 3.12 lint
job passes.
Task.get_stack(limit=32) walks coroutine frames synchronously and
measurably stalls the event loop when called for 50+ relay tasks during
a reconnect storm. The /debug/tasks snapshot does not need this by
default — the perf-cheap fields (name, coro_qualname, age, done) are
enough for the common case of "which tasks are alive".
- collect_tasks: add with_stack_depth=False kwarg; default leaves
stack_depth=None per entry.
- /debug/tasks: read ?stack=true query parameter and forward it. Default
response is cheap; opt-in explicitly when human-debugging one snapshot.
- Test: verify default entries have stack_depth=None and that
?stack=true produces at least one integer depth.
- Log the resolved Anthropic pre-upstream concurrency at startup so
operators can correlate pre_upstream_wait_ms lines with the configured
cap. Distinguishes auto-detected vs. explicit vs. unbounded.
- Remove the stage_timings, session_id, and stage_timings_path fields
from RequestLog. They were declared optional but never populated at
any of the three RequestLog construction sites — stage timings flow
exclusively through emit_stage_timings_log (structured log line) and
Prometheus histograms. Keeping unpopulated fields misled readers.
Option (A) from the review.
- Inline the one-liner collect_ws_sessions / collect_warmup pass-throughs
at their two callers in server.py; keep collect_tasks since it has
real logic. Unit tests were rewritten against the registry's own
snapshot()/to_dict() serializers.
On Linux dual-stack sockets (IPV6_V6ONLY=0 default), an IPv4 loopback
connection arrives as ::ffff:127.0.0.1 and was 404'd by the literal set
check — silent outage of /debug/* when the proxy binds to :: or 0.0.0.0.
Replace the set-membership check with ipaddress.ip_address(host).is_loopback,
special-casing 'localhost' (not an IP literal) and treating ValueError
(malformed input) as non-loopback. The None sentinel for TestClient / UDS
sockets is preserved.
Unit 5 of the Codex-proxy resilience plan. Three always-on
/debug/* endpoints make "what is this process doing right now?"
a single curl away:
- GET /debug/tasks serializes asyncio.all_tasks() to name,
coro_qualname, stack_depth, done, and age_seconds (derived from
the WS session registry for Codex relay tasks, null otherwise).
Sorted by age desc. No frame locals, no request bodies, no
coroutine args.
- GET /debug/ws-sessions returns WebSocketSessionRegistry.snapshot().
- GET /debug/warmup returns WarmupRegistry.to_dict().
A FastAPI Depends(require_loopback) dependency 404s (not 403) any
non-loopback caller so debug routes stay invisible to external
scanners. Dependency chosen over middleware because the route set
is small and explicit Depends(...) makes the guard visible to
reviewers and easy to override in tests.