rails ai-agents

Claude Code for Rails: Setup and Guardrails

21 min read

Set up Claude Code in Rails with CLAUDE.md, permissions, Skills, subagents, hooks, Ruby LSP, and review rules that keep edits controlled.

Claude Code for Rails developers - terminal and RubyMine workflow with CLAUDE.md, Skills, permissions, and RSpec

Claude Code is Anthropic's agentic coding tool: it reads your codebase, edits files, and runs commands from your terminal or IDE. For a Rails developer that means it can run a test file, draft a migration, trace a bug through your models, refactor a controller, and prepare a pull request, with writes and shell commands controlled by permissions. This post is about the tool and how to fit it into a Rails workflow, not about building your own agent.

If you want to embed an agent inside your own app instead, using the model as a feature your users interact with, that is a different job, and I have written about building AI agents in Ruby with the Anthropic SDK and about the Claude Agent SDK in Ruby separately. Claude Code is the coding assistant you run on your machine. The rest of this is the setup and the day-to-day patterns that make it useful on a Rails codebase.

Scope note: the parts most likely to drift are install methods, permission syntax, bundled Skills, plugin names, and IDE features. Treat exact flags as a starting point, check claude --version, and prefer the official docs when your local tool disagrees with this page.

What Claude Code Is for Rails Developers

Claude Code runs as a command-line tool (claude) and integrates with editors such as VS Code and JetBrains IDEs. Anthropic also ships desktop, browser, and mobile-adjacent surfaces, but a Rails project usually starts with the terminal CLI plus the editor you already use. Most interactive surfaces require a Claude subscription or an Anthropic Console account; check the current docs for the exact account path your team uses.

What it is good at: the tedious, well-scoped work that eats your afternoon. Writing specs for an untested service object, fixing RuboCop offenses across a directory, drafting a migration and the model changes that go with it, updating a gem and chasing the fallout, writing a first-draft commit message. It is not a replacement for understanding your own domain, and it will confidently do the wrong thing if your instructions or your codebase are ambiguous. You stay the reviewer.

Here is how I would compare it to the two tools many Rails teams already have open. This is a workflow comparison, not a claim that one tool is universally better.

Rails workflow question Claude Code Cursor GitHub Copilot
I need inline suggestions while typing Not the point Yes Strongest fit
I need a multi-file change with test output Strong fit: terminal loop, permissions, test commands Strong fit inside its editor Possible, but less natural
I need repo-specific repeatable procedures Skills, CLAUDE.md, hooks, subagents Rules and editor workflows Repo instructions and prompt files
I need shell or CI composition Strong fit with claude -p Weak fit Possible through separate Copilot tooling
I need the least workflow change Medium: a new CLI habit High if the team adopts Cursor High if Copilot is already installed

The rough division I use: Copilot for inline autocomplete while I type, Claude Code for anything that spans multiple files or needs to run a command and read the result. Cursor is the better fit when the team wants the AI workflow inside one editor. They are not mutually exclusive.

Setting Up Claude Code in a Rails Project

Install the CLI, drop a CLAUDE.md in the repo root, set a permission allowlist, and connect your editor. That is the basic setup.

Install with the native installer or Homebrew, then start it from your project root:

# Native install (macOS, Linux, WSL)
curl -fsSL https://claude.ai/install.sh | bash

# Or with Homebrew
brew install --cask claude-code

# Start it inside your Rails app
cd my_rails_app
claude

Start it from the repo root, not a subdirectory, so it loads the project CLAUDE.md and can see the whole tree.

The CLAUDE.md Project Memory File

CLAUDE.md is a markdown file Claude Code reads at the start of every session. Of everything in the setup, it pays off the most, because it is where you write down what you would otherwise re-explain every time. On a Rails app the file lives at ./CLAUDE.md or ./.claude/CLAUDE.md and gets committed so the whole team shares it. Personal, uncommitted notes go in ./CLAUDE.local.md (add it to .gitignore), and machine-wide preferences go in ~/.claude/CLAUDE.md.

Run /init to generate a first draft. Claude scans your codebase and writes down the build commands, test setup, and conventions it can find. Then trim it to the things it could not infer on its own. A good Rails CLAUDE.md is short and specific:

# MyApp

## Stack
- Ruby 3.4.7, Rails 8.0, PostgreSQL 16, Hotwire, Solid Queue.
- Tests: Minitest (not RSpec). Run with `bin/rails test`.
- Authorization: Pundit. Every controller action calls `authorize`.

## Conventions
- Business logic lives in `app/services/`, one public method `call`.
- Never write raw SQL in models; use scopes and Arel.
- Prefer `bin/rails g` generators, then edit, over hand-writing files.

## Commands
- Lint: `bundle exec rubocop -A`
- Type/db check before commit: `bin/rails db:migrate:status`

## Do not touch
- `db/schema.rb` by hand; regenerate it via migrations.
- Anything under `vendor/` or `config/credentials/`.

Keep it short enough to review. It loads into the context window every session, so a bloated file both costs tokens and, in practice, gets followed less reliably. If a section grows into a multi-step procedure, that is a signal to move it into a Skill (more on that below) or a path-scoped rule under .claude/rules/ instead. For example, a .claude/rules/testing.md with paths: ["test/**/*.rb"] in its frontmatter only loads when Claude touches a test file.

A newer feature worth knowing: alongside the CLAUDE.md you write, Claude Code keeps its own auto memory per repository, saving things it learns (the exact test command, a gotcha in your setup) across sessions without you writing anything. You can browse and edit all of it with the /memory command. The short version: write the facts, let /init bootstrap it, and prune regularly.

Permissions and the Allowlist

Claude Code asks before it edits a file or runs a shell command. That is the safety model, and on a Rails project you tune it so the common, safe things stop prompting while destructive things always ask. Manage rules with /permissions; they are stored in .claude/settings.json and can be committed for the team.

{
  "permissions": {
    "allow": [
      "Bash(bin/rails test *)",
      "Bash(bundle exec rspec *)",
      "Bash(bundle exec rubocop *)",
      "Bash(bin/rails g *)",
      "Bash(git commit *)"
    ],
    "deny": [
      "Read(config/credentials/**)",
      "Read(.env*)",
      "Bash(git push *)",
      "Bash(bin/rails db:drop *)"
    ]
  }
}

Rules are evaluated deny first, then ask, then allow, so a deny always wins. The deny reads on config/credentials/** and .env* matter: they keep your master key and secrets out of the model's context, and they apply to Claude's file tools and to shell reads like cat and sed. Read-only exploration usually feels fast; edits and risky commands should still feel deliberate.

There are also permission modes you switch between: default (prompt on first use of each tool), plan (read and explore only, no edits, good for "tell me how you would fix this" before it touches anything), and acceptEdits (auto-accept file edits). There is a bypassPermissions mode that skips prompts entirely; I only use it inside a throwaway container, never against a real repo.

Ruby LSP for Semantic Navigation

If your Claude Code install has the Ruby LSP plugin available, it is worth testing on a Rails codebase before you rely on text search alone. Grep is fine until you have three Invoice classes in different namespaces. Ruby LSP gives the tool semantic queries instead: list the classes and methods in a file, jump to where a symbol is defined, find usages, pull docs, and search symbols across the project. That turns "rename the Invoice module everywhere" from a fragile find-and-replace into a language-server-backed refactor.

The setup below is intentionally labeled as a checked example, not a permanent contract. Plugin names and settings move faster than Ruby itself. Confirm the plugin name with claude plugin list or the current Claude Code plugin docs before putting this in team setup instructions:

# Prerequisite: the language server gem (or add gem "ruby-lsp" to your Gemfile)
gem install ruby-lsp

# Install the official plugin
claude plugin install ruby-lsp@claude-plugins-official

Then enable it and the LSP tool in ~/.claude/settings.json and restart Claude Code:

{
  "env": { "ENABLE_LSP_TOOL": "1" },
  "enabledPlugins": { "ruby-lsp@claude-plugins-official": true }
}

Ruby LSP itself covers Ruby files and Rails-aware behavior through its Rails add-on. The part to verify in your environment is not whether Ruby LSP works; it is whether Claude Code is actually connected to it and using the LSP tool during the session.

Separately, if you run Claude Code inside VS Code or a JetBrains IDE, the editor integration also shares your language server's diagnostics, the same red underlines you see in the Problems panel, through a built-in ide connection (exposed to the model as mcp__ide__getDiagnostics). So when an edit breaks a method resolution, Claude can notice and correct itself on the next turn. The plugin gives it semantic navigation; the IDE connection gives it your live error list. They stack.

The test is simple: ask Claude to find every caller of one method, then check whether it used the LSP tool or fell back to text search. If it only greps, you still have a useful coding assistant, just not semantic navigation.

Using Skills for Rails Work

A Skill is a SKILL.md file that packages a repeatable procedure and loads only when you use it. You take a checklist or multi-step task you keep re-typing and turn it into a slash command your whole team can run. Because a Skill's body loads on demand rather than every session, a long reference procedure costs almost nothing until you invoke it, which is exactly why you move procedures out of CLAUDE.md and into Skills.

Skills live in .claude/skills/<name>/SKILL.md for a project or ~/.claude/skills/<name>/SKILL.md for your personal set. The directory name becomes the command. The frontmatter description tells Claude when the Skill is relevant, so it can load it automatically, or you invoke it directly with /name.

Here is a Rails-shaped example: a Skill that scaffolds a Pundit policy the way your team writes them.

---
description: Scaffold a Pundit policy and matching spec for a model. Use when the user asks to add authorization for a new model or resource.
---

## Instructions

When asked to add a policy for a model:

1. Create `app/policies/<model>_policy.rb` subclassing `ApplicationPolicy`.
2. Define `index?`, `show?`, `create?`, `update?`, `destroy?`, each
   scoped to `user` and `record`. Default deny; open up explicitly.
3. Add a `Scope` class that filters records to the current user's account.
4. Generate `test/policies/<model>_policy_test.rb` with a case per action,
   proving a user from another account is denied.
5. Run `bin/rails test test/policies/` and report the result.

Now /add-policy for Invoice produces the policy, the scoped query, and a test that proves cross-tenant isolation, in your house style. You can also inject live context: a line like !`git diff HEAD` in a Skill runs the command and inlines its output before Claude reads the Skill, which is how a /review-changes Skill grounds itself in your actual working tree.

Claude Code also ships bundled Skills you get for free, including /code-review, /debug, and /loop. And custom slash commands (the older .claude/commands/ files) have been merged into the Skills system, so anything you already had there keeps working.

In Your Editor: RubyMine and VS Code

Both major editor integrations do the same core thing: run Claude Code next to your code, show its edits as IDE diffs, and feed it your selection and diagnostics.

The VS Code extension is a native graphical panel, not just a terminal tab. The current docs list inline diffs, @-mentions, plan review, conversation history, and checkpoints. Install it from the Extensions view or the marketplace. If you also want to type claude in the integrated terminal, keep the standalone CLI installed too.

The JetBrains plugin is the editor path for RubyMine users. Install the Claude Code plugin from the JetBrains Marketplace, install the CLI separately, and run it inside the IDE. The useful pieces are IDE diffs, selection-context sharing, file references, and diagnostic sharing from the language server.

RubyMine support needs a caveat: the plugin documentation names several JetBrains IDEs but did not call out RubyMine by name when this page was checked. RubyMine is still a JetBrains IDE, but I would treat that as a support caveat, not a guarantee. If the plugin is rough in your setup, open RubyMine's built-in terminal and run claude there directly; you lose the in-IDE diff viewer but keep the terminal workflow.

Subagents: Isolating the Noisy Work

When a side task would flood your main conversation with search results or file dumps, Claude Code can hand it to a subagent that works in its own context window and returns only a summary. This is a helper agent inside the tool, not an agent in your app. You manage them with /agents, and they are markdown files in .claude/agents/ (project) or ~/.claude/agents/ (personal):

---
name: rails-test-writer
description: Writes and runs Minitest tests for Rails models and services. Use when asked to add test coverage.
tools: Read, Grep, Glob, Edit, Bash
model: sonnet
---

You write focused Minitest tests for this Rails app. Read the target
class, cover the happy path and the important edge cases, prove
authorization boundaries hold, and run `bin/rails test` on the files
you create. Return a short summary of what you added and the result.

Claude delegates to it when a task matches the description, or you invoke it explicitly. The win is context hygiene: the test-writing back-and-forth stays in the subagent's window, and your main session stays focused on the feature.

Real Rails Workflows

The four Rails jobs Claude Code handles best are writing and running tests, generating migrations with backfills, refactoring across files, and reviewing a diff. Each runs the same loop: it edits, runs a command behind your permission prompt, reads the output, and iterates. Here are the patterns I actually use.

Writing and running tests. "Write Minitest coverage for RefundService, then run it." Claude reads the service, writes the test file, runs bin/rails test, reads the failures, and iterates until green. Because the command runs behind a permission prompt you allowlisted, this is fast without being reckless. The subagent above makes it repeatable.

Migrations. "Add a status enum to Invoice with a backfill." Claude generates the migration, updates the model, and can run bin/rails db:migrate after you review the diff. I keep db:drop and db:reset denied so it can never blow away a database, even in development, without me typing yes.

Refactors across files. "Extract the PDF generation out of InvoicesController into a service object following our app/services/ convention." This is where whole-repo awareness pays off: it finds the callers, moves the logic, updates them, and runs the tests. The CLAUDE.md convention line ("one public method call") is what keeps the output in your house style instead of some generic pattern.

Code review. Run the bundled /code-review Skill on your working diff, or pipe a diff in headlessly:

git diff main --name-only | claude -p "review these changed files for N+1 queries, missing authorization, and unsafe migrations"

That claude -p (print/headless) mode is the same thing you would drop into a CI step or a git hook.

Hooks: Commands That Always Run

CLAUDE.md shapes what Claude tries to do, but it is guidance, not enforcement. When something must happen at a specific moment, use a hook: a shell command Claude Code runs at a lifecycle event, configured in .claude/settings.json. The events include PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, and more. The classic Rails use is auto-formatting after every edit:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          { "type": "command", "command": "bundle exec rubocop -A --force-exclusion \"$CLAUDE_FILE_PATHS\"" }
        ]
      }
    ]
  }
}

Now every file Claude edits gets RuboCop-corrected automatically, so its output matches your style guide whether or not it remembered to. A PreToolUse hook can go further and block edits to protected paths outright.

Connecting Your Own Tools with MCP

Claude Code supports the Model Context Protocol, so it can talk to external systems: your database, GitHub, Sentry, a Jira board. You add servers with claude mcp add (stdio, SSE, or HTTP transports), and project-level server config lives in a .mcp.json you can commit. A read-only Postgres MCP server, for instance, lets Claude inspect your actual schema and run explain plans while it writes a query, instead of guessing from schema.rb.

Start MCP read-only. A database server that can run SELECT queries is useful for schema inspection and query planning; a server that can mutate data is a different risk category. I would not give Claude write tools to billing, authorization, or customer data until the tool surface has explicit scopes, logs, and a human approval step.

Claude Code vs Cursor and Copilot for Rails

For Rails work the division is practical: Copilot is best when the task is completion while you type, Cursor is strongest when the team wants an AI-first editor, and Claude Code is strongest when the job is a terminal loop that edits, runs specs, reads output, and repeats. What Claude Code has that the other two do not emphasize as much is the customization stack (CLAUDE.md, Skills, subagents) plus shell composition: claude -p drops into CI, git hooks, and pipelines.

The pragmatic answer is that these overlap and you do not have to pick one. Many Rails developers run Copilot for completions and Claude Code for multi-file, test-running tasks in the same session.

Limitations and Gotchas

Claude Code's main limitations are four: it is not autonomous and needs every diff reviewed, long sessions cost tokens and money, permissions are guardrails not a sandbox, and the tooling changes weekly. Each one is workable once you know it is there.

It is not autonomous, and you should not treat it as if it were. CLAUDE.md is context, not a contract; the docs are explicit that Claude reads it and tries to follow it with no guarantee of strict compliance. Vague instructions produce vague results, and two contradictory lines in your memory files mean it picks one arbitrarily. The review step is not optional. Read every diff, especially on migrations and anything touching authorization or money.

Cost and context are real constraints. A long session accumulates conversation history, and every token is one you pay to process on the next turn. Use /compact when a session gets long, cap the scope of what you ask for, and lean on subagents to keep exploration out of your main context. If you are on a metered Console account rather than a subscription, a sprawling refactor can run up a bill faster than you expect. Route heavy reasoning to a stronger model with /model and keep routine edits on a cheaper tier.

Permissions are guardrails, not a sandbox. Deny rules stop Claude's own tools, but a bare Bash allow rule can still shell out to something that reads a file indirectly. For untrusted code or real enforcement, combine permission rules with the OS-level sandbox rather than relying on prompts alone. And never run bypassPermissions against a repo you care about.

The tooling changes weekly. Features get renamed, defaults shift, the JetBrains plugin is still Beta. Anything specific here can drift. Pin your expectations to claude --version and the current docs, not to a blog post, including this one.

When I do not reach for it. For a one-line fix I already understand, opening Claude Code is slower than just typing it. For work that needs real product judgment, a nuanced pricing change, a schema decision with downstream consequences, I do the thinking myself and use Claude Code to execute the mechanical parts once the decision is made. It is a very good pair of hands, not a substitute for knowing what you want built.

Team Rollout Checklist

For one developer, Claude Code can stay informal. For a team, I would not roll it out until these checks are true:

Check Why it matters
CLAUDE.md is committed and under review The shared instructions become part of the codebase, so changes need the same scrutiny as any other workflow file.
Secrets paths are denied A helpful model should not read credentials, .env files, or private keys while exploring.
Test and lint commands are allowlisted The useful loop is edit, run focused checks, read output, and fix. If every safe check prompts, people either stop using it or over-approve.
Destructive commands still ask db:drop, git push, deploys, billing scripts, and data migrations should require explicit review.
First use case is narrow Start with specs, lint fixes, or a contained refactor. Do not begin with a week-long rewrite.
Diff review is non-negotiable Permissions reduce accidental damage; they do not prove the change is correct.

The Adoption Curve for a Rails Team

Install the CLI, run /init in your Rails repo, and spend ten minutes trimming the generated CLAUDE.md to the handful of facts Claude cannot guess. Add a permission allowlist for rspec or bin/rails test and deny reads on your credentials. Connect whichever editor you already use so it sees your Ruby LSP diagnostics. Then give it one contained job, a spec for an untested class, and watch how it runs and reads the result. Add a Skill the first time you catch yourself pasting the same instructions twice. That progression, memory then permissions then Skills then subagents, is the whole adoption curve, and each step is small.

If you want to go the other direction and put an agent inside your product rather than on your keyboard, that is a genuinely different discipline with its own trade-offs around tools, authorization, and cost.

Rolling Claude Code out across a Rails team is mostly a writing job, not an installation one. The repo-specific CLAUDE.md, the allowed commands, the denied files, the Skill candidates, and the review rules are what make the tool behave consistently across a team. The editor integration is the part everyone starts with and the part that matters least.

Further Reading