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

  1. Delete the cassette file and re-run the test so it records only the interactions the current code actually makes (most common fix)
  2. Change the code or test so it exercises every recorded interaction, or split the cassette into smaller per-scenario cassettes
  3. Use the :drop_unused_requests cassette option (VCR.use_cassette('x', drop_unused_requests: true)) to prune unused interactions from the cassette on eject
  4. Temporarily set allow_unused_http_interactions: true while debugging, then remove it so the assertion protects you again
  5. 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

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


AI-assisted analysis of vcr/vcr@a747bb6478 (2026-08-21). Data as JSON: /api/errors/f15f94322de5fc9a. Report an issue: GitHub.