{"record":{"id":"f15f94322de5fc9a","repo":"vcr/vcr","slug":"there-are-unused-http-interactions-left-in-the-cas","errorCode":null,"errorMessage":"There are unused HTTP interactions left in the cassette:\n#{descriptions}","messagePattern":"There are unused HTTP interactions left in the cassette:\n#(.+?)","errorType":"exception","errorClass":"VCR::Errors::UnusedHTTPInteractionError","httpStatus":null,"severity":"error","filePath":"lib/vcr/cassette/http_interaction_list.rb","lineNumber":73,"sourceCode":"        @used_interactions.any? { |i| interaction_matches_request?(request, i) }\n      end\n\n      def remaining_unused_interaction_count\n        @interactions.size\n      end\n\n      # Checks if there are no unused interactions left.\n      #\n      # @raise [VCR::Errors::UnusedHTTPInteractionError] if not all interactions were played back.\n      def assert_no_unused_interactions!\n        return unless has_unused_interactions?\n        logger = Logger.new(nil)\n\n        descriptions = @interactions.map do |i|\n          \"  - #{logger.request_summary(i.request, @request_matchers)} => #{logger.response_summary(i.response)}\"\n        end.join(\"\\n\")\n\n        raise Errors::UnusedHTTPInteractionError, \"There are unused HTTP interactions left in the cassette:\\n#{descriptions}\"\n      end\n\n    private\n\n      # @return [Boolean] Whether or not there are unused interactions left in the list.\n      def has_unused_interactions?\n        @interactions.size > 0\n      end\n\n      def request_summary(request)\n        super(request, @request_matchers)\n      end\n\n      def matching_interaction_index_for(request)\n        @interactions.index { |i| interaction_matches_request?(request, i) }\n      end\n\n      def matching_used_interaction_for(request)","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/vcr/vcr/blob/a747bb6478674c71f189454083c3cf276f09042c/lib/vcr/cassette/http_interaction_list.rb#L55-L91","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before: cassette 'checkout' has 3 interactions, test makes 1 request\nVCR.use_cassette('checkout') do\n  client.create_order # only POST /orders is used\nend # => UnusedHTTPInteractionError listing GET /cart and POST /payments\n\n# after: re-record only what this test does\nVCR.use_cassette('checkout', record: :all, drop_unused_requests: true) do\n  client.create_order\nend\n# cassette now contains only POST /orders; later runs pass","handlingStrategy":"try-catch","validationCode":"cassette = VCR.insert_cassette('api', allow_unused_http_interactions: false)\n# before eject, check what is left\nunused = cassette.http_interactions.remaining_unused_interaction_count\nVCR.eject_cassette(skip_no_unused_interactions_assertion: true) if unused.positive?","typeGuard":null,"tryCatchPattern":"begin\n  VCR.use_cassette('api') { client.get }\nrescue VCR::Errors::UnusedHTTPInteractionError => e\n  warn \"stale cassette: #{e.message}\"\n  File.delete('cassettes/api.yml')\n  retry # re-record with only the interactions this test makes\nend","preventionTips":["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"],"tags":["vcr","cassette","unused-interactions","test-assertion","record-mode","ruby"],"backgroundTag":"unused-stub-detection","analyzedSha":"a747bb6478674c71f189454083c3cf276f09042c","analyzedAt":"2026-08-21T19:21:22.610Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}