test: regression suite for known-bad API request patterns from prior COEs - #798
test: regression suite for known-bad API request patterns from prior COEs#798scottwofford wants to merge 2 commits into
Conversation
…prior COEs Pins the transparency-first contract per pattern: known-bad fixtures pass through _prepare_request_kwargs verbatim, upstream 400 rejections are relayed as clean invalid_request_error (never a proxy 500), and context_management is forwarded via extra_body rather than stripped. Upstream behavior verified against the live Anthropic API on 2026-07-06. Trello: https://trello.com/c/mWjeUBG1 and https://trello.com/c/qGTbhaTa Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Code review — tests-only PR, well-craftedRead through the diff end-to-end and spot-checked the source that the tests reference ( What works well
Nits (non-blocking)
Coverage / correctness
Nice write-up in the PR body — treating the "definition of done" reinterpretation (line: "faithfully relaying the upstream 400 is the correct handling") as a first-class part of the audit deliverable is the right call. |
|
Claude-generated merge-queue triage of all open Luthien PRs, requested by Scott (Jul 7, 2026). Advisory only; Scott has not yet acted on these recommendations. Recommendation: merge. Tests plus changelog only, no production code. Beyond regression coverage, the module docstring pins the transparency contract (forward verbatim, relay upstream 400s cleanly), which is the architectural stance that closed #204. Useful to have in the suite before deciding #797 and #799, both of which get judged against exactly this contract. Caveat inherited from the PR body: the two "pattern flipped upstream" verdicts came from a live-API probe on Jul 6 and would need re-probing to re-confirm. |
Summary
COE audit deliverable covering two Trello cards:
Adds
tests/luthien_proxy/unit_tests/test_known_bad_request_patterns.py(19 tests) with each known-bad pattern as a verbatim fixture, plus a changelog fragment. No production code changes.Audit context
None of the LiteLLM-era sanitizer fixes (PRs #201, #167, #178, #151) survive on current
main(verified: those commits are not ancestors oforigin/main). That is not a regression to restore: the current direct-SDK architecture is deliberately transparency-first (_prepare_request_kwargsforwards unknown fields viaextra_body; the pipeline forwards the client'santhropic-betaheader per #269). The contract is "behavior through the proxy == behavior of a direct connection."To ground the verdicts, each pattern was probed against the live Anthropic API on 2026-07-06 (
claude-haiku-4-5), and the current-day Claude Code request shape was checked against a real recorded request in the local proxy's event log. Two historical patterns flipped: the API now accepts whitespace-only text blocks, andcontext_managementis now a real API feature (context editing) that Claude Code sends on every request — the old PR #151 "strip it" fix would today silently disable context editing.Per-pattern verdicts
invalid_request_error(never a proxy 500). Same outcome as a direct connection; current Claude Code no longer emits these.test_empty_text_block_survives_forwarding,test_pipeline_relays_400_end_to_end[empty-text-block]test_messages_and_tools_forwarded_unmodified[whitespace-text-block]test_orphaned_tool_result_survives_forwarding,test_pipeline_relays_400_end_to_end[orphaned-tool-result]scopestill rejected without matching beta headeranthropic-betaheader forwarded (#269), so beta-gated cache features work when the client opts in. Current Claude Code no longer sendsscopeon tools (verified against recorded request).test_cache_control_scope_survives_forwarding,test_pipeline_relays_400_end_to_end[cache-control-extra-field]truncationshape rejected; currenteditsshape acceptedcontext_management(edits shape) on every request; the proxy must forward it viaextra_body, and does.test_context_management_forwarded_via_extra_bodytest_messages_and_tools_forwarded_unmodified[duplicate-tools],test_pipeline_relays_400_end_to_end[duplicate-tools]SimpleLLMPolicyapplies judge decisions atmessage_delta, andvalidate_anthropic_event_orderingruns on every outbound stream (advisory +streaming.protocol_violationevent). Existing coverage:test_anthropic_stream_validator.py,e2e_tests/test_mock_simple_llm_parallel_tools.py.test_content_block_after_message_delta_is_flaggedNote: the two cards disagree on the 5th pattern (card mWjeUBG1 lists parallel tool_use ordering #356; card qGTbhaTa lists duplicate tools). This PR covers the union — 6 patterns.
On card mWjeUBG1's original definition of done ("no 400 returned to the client")
That definition was written for the old sanitize-and-repair architecture. Under the current transparency-first design, faithfully relaying the upstream 400 is the correct handling: the clients that generated these malformed requests have since been fixed upstream, a direct connection would 400 identically, and silent request rewriting is what the refactor deliberately removed. The tests therefore pin (a) verbatim forwarding and (b) clean 400 relay (never a proxy-side 500), rather than suppression of the 400. If we later decide the proxy should again repair malformed requests, these tests are the deliberate decision point — they will fail loudly and must be updated intentionally, not silently.
Test plan
uv run pytest tests/luthien_proxy/unit_tests/test_known_bad_request_patterns.py)./scripts/dev_checks.shclean (format, lint, pyright, full unit suite, complexity)🤖 Generated with Claude Code