canary-load: keep going on any exception, fail at the end if any collected - #38708
canary-load: keep going on any exception, fail at the end if any collected#38708bosconi wants to merge 3 commits into
Conversation
A `requests.ConnectionError` wrapping `NameResolutionError(... [Errno -3] Temporary failure in name resolution)` is caught by the connection handler, but its message matched nothing in CONNECTION_ERROR_STRINGS, so the handler re-raised and the run aborted. qa-canary MaterializeInc#982 (2026-09-02) lost 16 hours of coverage to one such resolver blip on the agent. Add the string so it restarts the chunk like the other transport failures. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KamXNLq4ze5CGpai1mGmrF
`http_sql_query` asserted `status_code == 200`. A bare AssertionError is caught by none of the workflow's handlers, so the balancer's 502 "upstream server not available" during the v26.40.1 cutover aborted qa-canary #986 at 4h54m and threw away the remaining 18 hours. Retry a 5xx a few times with a short sleep, since a restarting environmentd is the common cause, and turn any other non-200 into a FailedTestExecutionError so the chunk loop collects it like a SQL failure. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KamXNLq4ze5CGpai1mGmrF
…ected Three handlers decided the fate of an exception escaping a chunk: a connection error restarted the chunk, a testdrive or command failure was collected, and everything else propagated. Propagation aborted the 23h run and, because the rethrow of collected failures sits after the loop, dropped every failure collected before the abort from the annotation and the analytics DB. qa-canary MaterializeInc#982 and #986 both ended that way. Collapse the three handlers into one `except Exception` that classifies via `failure_messages`: a connection error still restarts the chunk and stays out of the verdict, a testdrive error still yields one entry per error, and any other exception is collected with its traceback as details. The run always reaches the end of its runtime and goes red if anything was collected. A psycopg OperationalError that is not a connection error, which used to re-raise, is now collected too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KamXNLq4ze5CGpai1mGmrF
QA LLM Review1. MEDIUM -- connection-error classification never sees the transport text for testdrive failures
Details
Observed in the qa-canary history: builds 913, 929 and 935 each went red with
Cheapest fix, matching the semantics the HTTP path already has: for message, details in failure_messages(e):
haystack = message if details is None else f"{message}\n{details}"
# TODO(def-): Remove when database-issues#6825 is fixed
if "Non-positive multiplicity in DistinctBy" in haystack:
continue
...
if is_connection_error(haystack):2. LOW -- collected testdrive errors lose their iteration/chunk attribution and location
The handler now rebuilds a fresh Details
Having |
Motivation
test/canary-loadis the 23-hour validator the qa-canary pipeline runs against the shared sandbox every night. Its chunk loop had three exception handlers, and anything they did not recognize propagated out of the loop. That did two things: it ended the run early, and, because the rethrow of collected failures sits after the loop, it dropped every failure collected before the abort from the annotation and the analytics DB. Two runs in the last week ended that way:AssertionError: <Response [502]> upstream server not available, the balancer's answer while environmentd restarted for the v26.40.1 cutover.http_sql_queryassertedstatus_code == 200, and a bareAssertionErrormatched no handler.Temporary failure in name resolutionfrom the agent's resolver. That arrives as arequests.ConnectionError, which the connection handler catches, but the message was not inCONNECTION_ERROR_STRINGS, so it re-raised.Description
Three commits, one per cause:
Temporary failure in name resolutiontoCONNECTION_ERROR_STRINGS, so the DNS blip restarts the chunk like the other transport failures.http_sql_query, retry a 5xx a few times with a short sleep, then turn any non-200 into aFailedTestExecutionErrorinstead of asserting. The chunk loop collects it like a SQL failure.except Exceptionthat classifies via a newfailure_messageshelper: connection errors restart the chunk and stay out of the verdict, testdrive errors yield one entry per error as before, and any other exception is collected with its traceback asdetails. The run always reaches the end of its runtime and goes red if anything was collected.One behavior change beyond the two failure cases: a psycopg
OperationalErrorwhose message is not a connection error used to re-raise and now is collected. Connection failures are still stdout-only and still do not fail the build; that is unchanged.Verification
No test file covers
test/canary-loadand the workflow needs the canary credentials, so verification was a local harness against the module:http_sql_querywithrequests.postmocked to return 502/502/200, 502 forever, 403 once, and 200; andfailure_messagesplus the handler body against a multi-errorFailedTestExecutionErrormixing a connection error, a real error and theDistinctByexclusion, aCommandFailureCausedUIError, psycopgOperationalErrorwith and without a connection string, the DNSConnectionError, the bareAssertionErrorfrom #986, and aKeyError. Each routed as described above.ruff,blackandpyrightare clean on the file.🤖 Generated with Claude Code
https://claude.ai/code/session_01KamXNLq4ze5CGpai1mGmrF