- Create the skill directory:
mkdir -p ~/.claude/skills/rails-repl - 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
Last active
January 28, 2026 21:00
-
-
Save dawson-conway/5371440e2c865e3e59194b084fbedc33 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 |
Run Ruby code in a persistent Rails console. Avoids the startup overhead of bin/rails runner for each command.
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"
fibin/rails runner ~/.claude/skills/rails-repl/claude_repl.rb &Wait briefly for startup, then verify PID file exists.
Write Ruby code to tmp/claude_repl/command.rb:
Tenant.first.namePoll 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; doneRead 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
Delete output.txt after reading (or before next command).
Write __EXIT__ to command.rb. The REPL will shut down gracefully and clean up its PID file.
| 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 |
Set CLAUDE_REPL_DIR to change the working directory (default: Rails.root/tmp/claude_repl).
- If REPL dies, PID file may be stale. Check with
kill -0. - If
command.rbdoesn't disappear after 30s, REPL may have crashed. Checkaudit.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