Skip to content

Instantly share code, notes, and snippets.

@coorasse
Last active March 10, 2026 21:31
Show Gist options
  • Select an option

  • Save coorasse/9ebf421447cc8bcf5b3d93399794eb49 to your computer and use it in GitHub Desktop.

Select an option

Save coorasse/9ebf421447cc8bcf5b3d93399794eb49 to your computer and use it in GitHub Desktop.
Record claude sessions outputs and replay them. VCR for Claude
# frozen_string_literal: true
require "open3"
require "yaml"
# Provides VCR-like recording and replay for Claude CLI calls.
#
# Usage:
# it "calls Claude" do
# with_claude_cassette("my_test/scenario")
# # ... code that calls ClaudeClient ...
# end
#
# On first run the real Claude CLI is invoked and the response is saved to
# spec/claude_cassettes/<name>.yml. On subsequent runs the saved response
# is replayed. Delete the cassette file to re-record.
module ClaudeCassette
CASSETTE_DIR = Rails.root.join("spec/claude_cassettes")
def with_claude_cassette(name)
cassette_path = CASSETTE_DIR.join("#{name}.yml")
@_claude_cassette = {}
if cassette_path.exist?
replay_claude_cassette(cassette_path)
else
record_claude_cassette(cassette_path)
end
end
# Returns the prompt input that was sent to Claude in the current test run.
def claude_cassette_input
@_claude_cassette&.dig("input")
end
private
def replay_claude_cassette(path)
recorded = YAML.load_file(path, permitted_classes: [Symbol])
cassette = @_claude_cassette
status = instance_double(Process::Status, success?: recorded["exit_status"].zero?,
exitstatus: recorded["exit_status"])
allow(Open3).to receive(:capture3).and_wrap_original do |original, *args|
if args.first != "claude"
next original.call(*args)
else
cassette["input"] = args.last
[recorded["stdout"], recorded["stderr"], status]
end
end
end
def record_claude_cassette(path)
cassette = @_claude_cassette
allow(Open3).to receive(:capture3).and_wrap_original do |original, *args|
stdout, stderr, status = original.call(*args)
if args.first == "claude"
cassette.merge!(
"input" => args.last,
"stdout" => stdout,
"stderr" => stderr,
"exit_status" => status.exitstatus,
"recorded_at" => Time.current.iso8601
)
FileUtils.mkdir_p(path.dirname)
File.write(path, cassette.to_yaml)
end
[stdout, stderr, status]
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment