Stop nil VCR filters flaking replays, and spell the MCP plural - #436
Merged
Conversation
Two fixes to the same family of papercut. **VCR filters registered against unset env vars.** Every filter_sensitive_data block read an ENV var directly, and CI sets none of them — cassettes replay without credentials — so on CI each block returned nil. VCR registers the filter anyway and then substitutes an empty string through every request and response it handles, which surfaces far from the cause as an intermittent NoMethodError: undefined method `each_key' for nil webmock/util/hash_validator.rb:12:in `validate_keys' while WebMock builds a stubbed response. It reads as a flaky cassette rather than a configuration problem, and it cost a re-run on PR #435 and an investigation on #432 to rule out. A filter_env helper now registers a filter only when the variable actually holds something, so replay is deterministic on CI and redaction still applies when recording with real keys. This also removes the one-off `if ENV[...]` guard that already existed for AZURE_OPENAI_RESOURCE — that guard was the right instinct applied to exactly one of ten filters. **The MCP plural did not inflect.** An acronym only matches a whole word, so with `inflect.acronym "MCP"` alone, `mcps` still camelizes to `Mcps`, and a constant spelled `MCPs` underscores back to `mc_ps_examples_test` — a name no file has. Registering the plural as its own acronym makes both directions agree (mcps <-> MCPs), and the two test classes that spelled it `Mcps` are renamed to match. Note `mcps:` stays lowercase as a prompt parameter, and `Mcp-Session-Id` stays as-is: that is the spelling the MCP specification gives the header. Engine suite 340 runs / 0 failures; docs tests match their pre-change baseline exactly (15 runs, 9 local credential errors before and after). RuboCop clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01He34kWksjpqPPDpCCqJq2C
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two fixes to the same family of papercut. Both surfaced while diagnosing CI failures on #432, #434 and #435.
1. VCR filters registered against unset env vars
Every
filter_sensitive_datablock read an ENV var directly:CI sets none of them — cassettes replay without credentials — so on CI every block returns
nil. VCR registers the filter anyway and then substitutes an empty string through every request and response it handles. That surfaces far from the cause as an intermittent:…while WebMock builds a stubbed response. It reads as a flaky cassette rather than a configuration problem. It failed
test (3.3, rails8)on #435 (passed on re-run) and cost an investigation on #432 to rule out.A
filter_envhelper now registers a filter only when the variable actually holds something. Replay is deterministic on CI; redaction still applies when recording with real keys.This also generalises the one-off
if ENV["AZURE_OPENAI_RESOURCE"]guard that already existed — the right instinct, applied to exactly one of ten filters.2. The MCP plural did not inflect
An acronym only matches a whole word, so
inflect.acronym "MCP"alone does not give the plural:mcps.camelizeMcpsMCPsmcps_examples_test.camelizeMcpsExamplesTestMCPsExamplesTestMCPsExamplesTest.underscoremc_ps_examples_test❌mcps_examples_test✅That last row is the real bug: a constant spelled
MCPsunderscored back to a name no file has. Registering the plural as its own acronym makes both directions agree, and the two test classes spelling itMcpsare renamed to match.Deliberately unchanged:
mcps:stays lowercase as a prompt parameter, andMcp-Session-Idkeeps its spelling — that is what the MCP specification names the header.ToolChoiceMcpis an external OpenAI SDK constant.The engine is unaffected by the new acronyms:
action_agent.inflectionsalready isolates every path under the engine root from host-registered acronyms, precisely so a host declaring"API"or"MCP"cannot break its constant lookups.Verification
actionagent/test: 340 runs, 0 failures.env.test)🤖 Generated with Claude Code
https://claude.ai/code/session_01He34kWksjpqPPDpCCqJq2C