Skip to content

Instantly share code, notes, and snippets.

@dawson-conway
Last active January 28, 2026 21:00
Show Gist options
  • Select an option

  • Save dawson-conway/5371440e2c865e3e59194b084fbedc33 to your computer and use it in GitHub Desktop.

Select an option

Save dawson-conway/5371440e2c865e3e59194b084fbedc33 to your computer and use it in GitHub Desktop.

Installation

  1. Create the skill directory:
    mkdir -p ~/.claude/skills/rails-repl
  2. Download both files to that directory:
    ~/.claude/skills/rails-repl
    curl -O <raw-gist-url>/SKILL.md
    curl -O <raw-gist-url>/claude_repl.rb
# Claude REPL - A persistent Rails console for Claude Code
#
# Run with: bin/rails runner ~/.claude/skills/rails-repl/claude_repl.rb
#
# Configuration:
# CLAUDE_REPL_DIR - Directory for runtime files (default: Rails.root/tmp/claude_repl)
require 'fileutils'
require 'stringio'
REPL_DIR = Pathname.new(ENV.fetch("CLAUDE_REPL_DIR") { Rails.root.join("tmp/claude_repl") })
COMMAND_FILE = REPL_DIR.join("command.rb")
OUTPUT_FILE = REPL_DIR.join("output.txt")
PID_FILE = REPL_DIR.join("pid")
AUDIT_LOG = REPL_DIR.join("audit.log")
ERROR_MARKER = "__REPL_ERROR__"
RESULT_MARKER = "__RESULT__"
EXIT_COMMAND = "__EXIT__"
FileUtils.mkdir_p(REPL_DIR)
def audit(message)
File.open(AUDIT_LOG, "a") { |f| f.puts(message) }
end
def audit_separator
audit("=" * 80)
end
def log(message)
timestamp = Time.current.strftime("%Y-%m-%d %H:%M:%S")
audit("[#{timestamp}] #{message}")
end
def cleanup
log("REPL STOPPED (PID #{Process.pid})")
PID_FILE.delete if PID_FILE.exist?
end
def execute_with_capture(code)
captured = StringIO.new
old_stdout, old_stderr = $stdout, $stderr
$stdout = $stderr = captured
error = nil
return_value = nil
begin
return_value = eval(code, TOPLEVEL_BINDING, "(claude_repl)")
rescue Exception => e
error = e
ensure
$stdout, $stderr = old_stdout, old_stderr
end
[captured.string, return_value, error]
end
# Handle shutdown signals
%w[INT TERM].each do |signal|
trap(signal) do
cleanup
exit
end
end
# Write PID file
PID_FILE.write(Process.pid.to_s)
audit_separator
log("REPL STARTED (PID #{Process.pid})")
log("Watching: #{COMMAND_FILE}")
audit_separator
loop do
if COMMAND_FILE.exist?
code = COMMAND_FILE.read.strip
audit_separator
log("COMMAND:")
audit(code)
# Handle exit command
if code == EXIT_COMMAND
OUTPUT_FILE.write("#{RESULT_MARKER}\nREPL shutting down")
COMMAND_FILE.delete
cleanup
exit
end
captured_output, return_value, error = execute_with_capture(code)
# Build output: stdout first, then either result or error
full_output = ""
full_output += captured_output unless captured_output.empty?
if error
full_output += "#{ERROR_MARKER}\n#{error.class}: #{error.message}\n#{error.backtrace.first(20).join("\n")}"
else
full_output += "#{RESULT_MARKER}\n#{return_value.inspect}"
end
log("OUTPUT:")
audit(full_output)
audit_separator
# Write output, then delete command file (signals completion)
OUTPUT_FILE.write(full_output)
COMMAND_FILE.delete
end
sleep 0.1
end
name description
rails-repl
Use when you need to run Ruby code in a Rails project - evaluates code in a persistent console without startup overhead

Rails REPL

Run Ruby code in a persistent Rails console. Avoids the startup overhead of bin/rails runner for each command.

Usage

1. Check if REPL is running

REPL_DIR="${CLAUDE_REPL_DIR:-$(pwd)/tmp/claude_repl}"
PID_FILE="$REPL_DIR/pid"
if [ -f "$PID_FILE" ] && kill -0 $(cat "$PID_FILE") 2>/dev/null; then
  echo "REPL running"
else
  echo "REPL not running"
fi

2. Start REPL if needed

bin/rails runner ~/.claude/skills/rails-repl/claude_repl.rb &

Wait briefly for startup, then verify PID file exists.

3. Send a command

Write Ruby code to tmp/claude_repl/command.rb:

Tenant.first.name

4. Wait for completion

Poll until command.rb disappears (max 30 seconds):

REPL_DIR="${CLAUDE_REPL_DIR:-$(pwd)/tmp/claude_repl}"
while [ -f "$REPL_DIR/command.rb" ]; do sleep 0.2; done

5. Read result

Read tmp/claude_repl/output.txt. The output format:

  • Success: Any stdout appears first, then __RESULT__ on its own line, then the return value
  • Error: Starts with __REPL_ERROR__, followed by exception message and backtrace

6. Clean up

Delete output.txt after reading (or before next command).

7. Stop REPL

Write __EXIT__ to command.rb. The REPL will shut down gracefully and clean up its PID file.

Files

File Purpose
command.rb Input - write Ruby code here
output.txt Output - result or error
pid REPL process ID
audit.log Full history of all commands and outputs

Configuration

Set CLAUDE_REPL_DIR to change the working directory (default: Rails.root/tmp/claude_repl).

Error Handling

  • If REPL dies, PID file may be stale. Check with kill -0.
  • If command.rb doesn't disappear after 30s, REPL may have crashed. Check audit.log.
  • Restart REPL if needed: kill old process, delete PID file, start fresh.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment