vcr/vcr · error · VCR::Errors::UnusedHTTPInteractionError
There are unused HTTP interactions left in the cassette: #{d
Error message
There are unused HTTP interactions left in the cassette:
#{descriptions} What it means
When a cassette is ejected with allow_unused_http_interactions: false (the default), VCR::HTTPInteractionList#assert_no_unused_interactions! raises VCR::Errors::UnusedHTTPInteractionError listing every recorded interaction that was never played back (lib/vcr/cassette/http_interaction_list.rb:73). This is an assertion that your test exercised all recorded HTTP traffic, not just a runtime failure.
Source
Thrown at lib/vcr/cassette/http_interaction_list.rb:73
@used_interactions.any? { |i| interaction_matches_request?(request, i) }
end
def remaining_unused_interaction_count
@interactions.size
end
# Checks if there are no unused interactions left.
#
# @raise [VCR::Errors::UnusedHTTPInteractionError] if not all interactions were played back.
def assert_no_unused_interactions!
return unless has_unused_interactions?
logger = Logger.new(nil)
descriptions = @interactions.map do |i|
" - #{logger.request_summary(i.request, @request_matchers)} => #{logger.response_summary(i.response)}"
end.join("\n")
raise Errors::UnusedHTTPInteractionError, "There are unused HTTP interactions left in the cassette:\n#{descriptions}"
end
private
# @return [Boolean] Whether or not there are unused interactions left in the list.
def has_unused_interactions?
@interactions.size > 0
end
def request_summary(request)
super(request, @request_matchers)
end
def matching_interaction_index_for(request)
@interactions.index { |i| interaction_matches_request?(request, i) }
end
def matching_used_interaction_for(request)View on GitHub (pinned to a747bb6478)
Solutions
- Delete the cassette file and re-run the test so it records only the interactions the current code actually makes (most common fix)
- Change the code or test so it exercises every recorded interaction, or split the cassette into smaller per-scenario cassettes
- Use the :drop_unused_requests cassette option (VCR.use_cassette('x', drop_unused_requests: true)) to prune unused interactions from the cassette on eject
- Temporarily set allow_unused_http_interactions: true while debugging, then remove it so the assertion protects you again
- Tighten request matching only if the 'unused' interactions are actually duplicates that should match the same request
Example fix
# before: cassette 'checkout' has 3 interactions, test makes 1 request
VCR.use_cassette('checkout') do
client.create_order # only POST /orders is used
end # => UnusedHTTPInteractionError listing GET /cart and POST /payments
# after: re-record only what this test does
VCR.use_cassette('checkout', record: :all, drop_unused_requests: true) do
client.create_order
end
# cassette now contains only POST /orders; later runs pass Defensive patterns
Strategy: try-catch
Validate before calling
cassette = VCR.insert_cassette('api', allow_unused_http_interactions: false)
# before eject, check what is left
unused = cassette.http_interactions.remaining_unused_interaction_count
VCR.eject_cassette(skip_no_unused_interactions_assertion: true) if unused.positive? Try / catch
begin
VCR.use_cassette('api') { client.get }
rescue VCR::Errors::UnusedHTTPInteractionError => e
warn "stale cassette: #{e.message}"
File.delete('cassettes/api.yml')
retry # re-record with only the interactions this test makes
end Prevention
- Keep one scenario per cassette; split god-cassettes so every recorded interaction belongs to a test that plays it
- After changing which requests code makes, delete affected cassettes and re-record rather than letting :new_episodes accumulate
- Use :drop_unused_requests when you want cassettes to self-prune on eject
When it happens
Trigger: A cassette recorded 3 interactions but the test made only 1 request (early return, skipped branch, stubbed internal call); record: :new_episodes added interactions on one run and the code path changed on the next; conditional logic (if/else) that hits different endpoints per run; VCR specs where the cassette name is reused across different tests via metadata, accumulating interactions.
Common situations: Refactoring changed which API calls a feature makes while the old cassette still contains them; feature flags or A/B branches; flaky order-dependent tests; deliberately abbreviated manual runs against a shared cassette; growing cassettes under :new_episodes that are never pruned.
Related errors
- #{record_mode} is not a valid cassette record mode. Valid m
- There is already a cassette with the same name (#{name}). Y
- VCR is turned off. You must turn it on before you can inser
- You passed the following invalid options to VCR::Cassette.ne
- The requested VCR cassette serializer (#{name.inspect}) is n
AI-assisted analysis of vcr/vcr@a747bb6478 (2026-08-21).
Data as JSON: /api/errors/f15f94322de5fc9a.
Report an issue: GitHub.