Skip to content

Stop nil VCR filters flaking replays, and spell the MCP plural - #436

Merged
TonsOfFun merged 1 commit into
mainfrom
fix/vcr-filters-and-mcp-inflection
Sep 11, 2026
Merged

Stop nil VCR filters flaking replays, and spell the MCP plural#436
TonsOfFun merged 1 commit into
mainfrom
fix/vcr-filters-and-mcp-inflection

Conversation

@TonsOfFun

Copy link
Copy Markdown
Contributor

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_data block read an ENV var directly:

config.filter_sensitive_data("ACCESS_TOKEN") { ENV["OPEN_AI_ACCESS_TOKEN"] }

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:

NoMethodError: undefined method `each_key' for nil
  webmock-3.26.4/lib/webmock/util/hash_validator.rb:12:in `validate_keys'
  webmock/response.rb:80:in `options='

…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_env helper 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:

input before after
mcps.camelize Mcps MCPs
mcps_examples_test.camelize McpsExamplesTest MCPsExamplesTest
MCPsExamplesTest.underscore mc_ps_examples_test mcps_examples_test

That last row is the real bug: a constant spelled MCPs underscored back to a name no file has. Registering the plural as its own acronym makes both directions agree, and the two test classes spelling it Mcps are renamed to match.

Deliberately unchanged: mcps: stays lowercase as a prompt parameter, and Mcp-Session-Id keeps its spelling — that is what the MCP specification names the header. ToolChoiceMcp is an external OpenAI SDK constant.

The engine is unaffected by the new acronyms: action_agent.inflections already 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
  • The two renamed docs test files match their pre-change baseline exactly (15 runs, 9 errors both before and after — local missing-credential errors, since this machine has no .env.test)
  • RuboCop clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01He34kWksjpqPPDpCCqJq2C

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
@TonsOfFun
TonsOfFun merged commit 76b6b74 into main Sep 11, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant