Fix two flaky ubuntu CI tests: revoked-token keep-alive artifact + StopsInFlight collector race - #544
Conversation
…ifact + StopsInFlight collector race Flake 2 (production): LocalPermissionBridge.RevokeReviewerToken removed the HttpListener prefix as well as the dictionary grant. On the managed (Linux/macOS) HttpListener, a request on a reused keep-alive connection to a just-removed prefix no longer routes to HandleAsync and yields a transport artifact — a spurious empty-body 200 or a connection reset — instead of the intended 404. The dictionary removal is already the authoritative revocation (HandleAsync re-validates every request against _reviewerTokens), so keep the prefix registered and let the dict miss produce a deterministic 404. Reproduced at ~4% per request over an 8000-iteration stress loop; 0 after the fix. Adds a keep-alive regression test. Flake 1 (test): AgentActionServiceTests collected StopsInFlight pushes into a plain List<T> written from both the test thread (RequestStop) and a threadpool thread (RunStopAsync completion) while the test thread polled Count/[^1] — a torn read during a concurrent Add NRE'd. Replaced the shared List with a lock-guarded StopStateRecorder across all sibling tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR Summary by QodoFix Ubuntu CI flakes: keep HttpListener prefixes on revoke + thread-safe StopsInFlight capture
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1.
|
…warning qodo flagged that keeping the HttpListener prefix on revoke makes the prefix set grow for the daemon's lifetime (bounded by reviewer-launch count). Removing the prefix is not an option — it reintroduces the keep-alive routing artifact this PR fixes — and the per-token prefix is a deliberate defense-in-depth layer, so a single-prefix redesign is out of scope. Instead make the growth diagnosable: log a Warning each time the listener's prefix count crosses a 1024 step, so runaway accumulation in a very long-lived daemon is observable rather than silent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes two independent, pre-existing intermittent failures on the ubuntu CI leg. Tracked in Linear as AI-1868.
Flake 2 (production defect) —
LocalPermissionBridgeTests.Revoked_reviewer_token_returns_404RevokeReviewerTokenremoved theHttpListenerprefix in addition to the dictionary grant. On the managed (Linux/macOS)HttpListener, a request on a reused keep-alive connection to a just-removed prefix no longer routes toHandleAsyncand yields a transport-level artifact — a spurious empty-body 200 or a connection reset — instead of the intended 404.The issue's original hypothesis (a dict revocation race between two lookup sites) does not hold: both requests hit the same general handler, and a live codex token would auto-approve both tools (200/200), a revoked one → 404/404 — so the observed
r1=404 / r2=200split can't come from the dict. Instrumentation confirmed every anomalousr2=200had an empty body andReviewerTokenCountForTest == 0, i.e. the request was never admitted by our code.Fix: remove the grant from
_reviewerTokensonly; do not remove the listener prefix.HandleAsyncalready re-validates every request against the dictionary, so a dict miss is a deterministic 404, and keeping the prefix ensures the revoked-token request still routes to our handler. Prefixes are freed when the listener closes; their count is bounded by the daemon's reviewer-launch count.Revoked_token_requests_404_on_reused_keepalive_connection(200 register→revoke→request cycles over one reused keep-alive client).Flake 1 (test defect) —
AgentActionServiceTests.Second_request_same_id_while_inflight_is_noop(and siblings)The tests collected
StopsInFlightpushes into a plainList<T>.AgentActionServicepushes synchronously on the test thread (RequestStop) and on a threadpool thread (RunStopAsynccompletion) —BehaviorSubjectinvokes subscribers on whichever thread callsOnNext— while the test thread pollsCount/[^1]. A concurrentAddduring an internal array resize leaves a torn backing store →NullReferenceExceptionon read (load-dependent, CI-only).Fix: replaced the shared
List<T>with a lock-guardedStopStateRecorderacross all eight sibling tests in the file.Verification
LocalPermissionBridgeTests: 61/61 green.AgentActionServiceTests: 19/19 green across 5 consecutive runs.