Why Rails Informant? ◆ Quick Start ◆ Configuration ◆ MCP Server ◆ Architecture ◆ Data and Privacy ◆ Security
Captures exceptions, stores them in your app's database with rich context (backtraces, breadcrumbs, request data), sends notifications, and exposes error data via a bundled MCP server -- so Claude Code and Devin AI can query, triage, and fix production errors directly.
No dashboard. The agent is the interface.
- Agent-native -- 12 MCP tools let AI agents list, inspect, resolve, and fix errors without a browser. The
/informantClaude Code skill provides a complete triage-to-fix workflow. - Self-hosted -- Errors stay in your database. No external service, no data leaving your infrastructure (unless you configure Slack, webhook, or Devin notifications).
- Zero-config capture -- Errors captured automatically via
Rails.errorsubscriber and Rack middleware. Breadcrumbs fromActiveSupport::Notificationsprovide structured debugging context. - Autonomous fixing -- Devin AI integration triggers investigation sessions on new errors, writes fixes with tests, and opens draft PRs. Humans retain the merge button.
- Lightweight -- Two database tables, no Redis, no background workers beyond ActiveJob. Runtime dependencies: Rails 8.1+ only.
Add to your Gemfile:
gem "rails-informant"Install:
bundle install
bin/rails generate rails_informant:install
bin/rails db:migrateSet an authentication token:
bin/rails credentials:editrails_informant:
api_token: your-secret-token # generate with: openssl rand -hex 32Install your AI agent integration:
bin/rails generate rails_informant:skill # Claude Code
bin/rails generate rails_informant:devin # Devin AIErrors are captured automatically in non-local environments. To capture errors manually:
RailsInformant.capture(exception, context: { order_id: 42 })# config/initializers/rails_informant.rb
RailsInformant.configure do |config|
config.capture_errors = !Rails.env.local?
config.api_token = Rails.application.credentials.dig(:rails_informant, :api_token)
config.slack_webhook_url = Rails.application.credentials.dig(:rails_informant, :slack_webhook_url)
config.retention_days = 30
endEvery option can be set via an environment variable. The initializer takes precedence over env vars. These configure the Rails app. For MCP server env vars (agent side), see MCP Server > Setup.
| Option | Env var | Default | Description |
|---|---|---|---|
api_token |
INFORMANT_API_TOKEN |
nil |
Authentication token for MCP server access |
capture_errors |
INFORMANT_CAPTURE_ERRORS |
true |
Enable/disable error capture (set to "false" to disable) |
devin_api_key |
INFORMANT_DEVIN_API_KEY |
nil |
Devin AI API key for autonomous error fixing |
devin_playbook_id |
INFORMANT_DEVIN_PLAYBOOK_ID |
nil |
Devin playbook ID for error triage workflow |
ignored_exceptions |
INFORMANT_IGNORED_EXCEPTIONS |
[] |
Exception classes to skip (comma-separated in env var) |
retention_days |
INFORMANT_RETENTION_DAYS |
nil |
Auto-purge resolved errors after N days |
slack_webhook_url |
INFORMANT_SLACK_WEBHOOK_URL |
nil |
Slack incoming webhook URL |
capture_user_email |
(none) | false |
Capture email from detected user (PII -- opt-in) |
webhook_url |
INFORMANT_WEBHOOK_URL |
nil |
Generic webhook URL for notifications |
Connecting the tokens: The
api_tokenin your Rails credentials andINFORMANT_PRODUCTION_TOKENmust be the same value. The first authenticates incoming requests to your app; the second tells the MCP server what token to send.
Secrets hygiene:
.envrccontains secrets and should be in.gitignore..mcp.jsonis safe to commit -- it only contains the command name, no tokens.
Errors are captured automatically via:
Rails.errorsubscriber -- background jobs, mailer errors,Rails.error.handleblocks- Rack middleware -- unhandled request exceptions and rescued framework exceptions
Errors are grouped by SHA256(class_name:first_app_backtrace_frame). Line numbers are normalized so the same error at different lines groups together.
Common framework exceptions (404s, CSRF, etc.) are ignored by default. Add more:
config.ignored_exceptions = ["MyApp::BoringError", /Stripe::/]Structured events from ActiveSupport::Notifications are captured automatically as breadcrumbs -- SQL query names, cache hits, template renders, HTTP calls, job executions. Stored per-occurrence for rich debugging context without raw log lines.
The bundled informant-mcp executable connects Claude Code to your error data via Model Context Protocol (MCP).
The rails_informant:skill generator creates .mcp.json automatically. Set INFORMANT_PRODUCTION_URL and INFORMANT_PRODUCTION_TOKEN as environment variables (e.g., via .envrc + direnv). The MCP server inherits env vars from your shell.
.mcp.jsonis used by Claude Code. Devin AI configures MCP servers through the Devin MCP Marketplace.
For multi-environment setups, create ~/.config/informant-mcp.yml:
environments:
production:
url: https://myapp.com
token: ${INFORMANT_PRODUCTION_TOKEN}
staging:
url: https://staging.myapp.com
token: ${INFORMANT_STAGING_TOKEN}| Tool | Description |
|---|---|
list_environments |
List configured environments |
list_errors |
List error groups with filtering and pagination |
get_error |
Full error detail with recent occurrences |
resolve_error |
Mark as resolved |
ignore_error |
Mark as ignored |
reopen_error |
Reopen a resolved/ignored error |
mark_fix_pending |
Mark with fix SHA for auto-resolve on deploy |
mark_duplicate |
Mark as duplicate of another group |
delete_error |
Delete group and occurrences |
annotate_error |
Add investigation notes |
get_informant_status |
Summary with counts and top errors |
list_occurrences |
List occurrences with filtering |
The MCP server enforces HTTPS by default. When pointing at a local HTTP URL (e.g., http://localhost:3000), pass --allow-insecure:
{
"mcpServers": {
"informant": {
"command": "informant-mcp",
"args": ["--allow-insecure"]
}
}
}This is only needed for local development/testing. Production setups over HTTPS don't need it.
Use /informant in Claude Code to triage and fix errors interactively. The skill:
- Checks error status with
get_informant_status - Lists unresolved errors
- Investigates with full occurrence data
- Implements fixes with test-first workflow
- Marks
fix_pendingfor auto-resolution on deploy
Automate error investigation and fixing with Devin AI. When a new error is captured, Rails Informant creates a Devin session that investigates via MCP tools, writes a fix with tests, and opens a draft PR.
-
Add the
informant-mcpserver to Devin's MCP Marketplace with your API URL and token. -
Upload the playbook installed at
.devin/error-triage.devin.mdto Devin and note the playbook ID. See Creating Playbooks. -
Configure Rails Informant:
RailsInformant.configure do |config|
config.devin_api_key = Rails.application.credentials.dig(:rails_informant, :devin_api_key)
config.devin_playbook_id = "your-playbook-id"
end- Triggers on the first occurrence only -- repeated occurrences of the same error do not create additional Devin sessions.
- Sends error class, message (truncated to 500 chars), severity, backtrace (first 5 frames), and error group ID.
- Devin connects to your MCP server to investigate errors, then either opens a draft PR with a fix or annotates the error with investigation findings.
The notification prompt includes: error class, error message (truncated), severity, occurrence count, timestamps, controller action or job class, backtrace frames, and git SHA. It does not include request parameters, user context, or PII.
Development Machine Remote Servers
+-----------------------+ +-----------------------+
| Claude Code | | Production |
| | | | /informant |
| | stdio | +-----------------------+
| v | HTTPS+Token
| MCP Server | -----------> +-----------------------+
| (exe/informant-mcp) | | Staging |
| | | /informant |
+-----------------------+ +-----------------------+
Inside the Rails app:
+-------------------------------------------------+
| Rails.error subscriber (primary capture) |
| Rack Middleware (safety net) |
| - ErrorCapture (before ShowExceptions) |
| - RescuedExceptionInterceptor (after Debug) |
| | |
| v |
| Fingerprint + Upsert (atomic counter) |
| | |
| v |
| Occurrence.create (with breadcrumbs, context) |
| | |
| v |
| NotifyJob.perform_later (async dispatch) |
| - Slack (Block Kit, Net::HTTP) |
| - Webhook (PII stripped by default) |
| - Devin AI (creates investigation session) |
+-------------------------------------------------+
unresolved --> fix_pending --> resolved (auto, on deploy)
unresolved --> resolved (manual)
unresolved --> ignored
unresolved --> duplicate
resolved --> unresolved [REGRESSION]
fix_pending --> unresolved (reopen)
ignored --> unresolved (reopen)
duplicate --> unresolved (reopen)
On boot, the engine checks if fix_pending errors have been deployed by comparing the current git SHA against original_sha. Deployed fixes are automatically transitioned to resolved.
Git SHA is resolved from environment variables (GIT_SHA, REVISION, KAMAL_VERSION) or .git/HEAD.
bin/rails informant:stats # Show error monitoring statistics
bin/rails informant:purge # Purge resolved errors older than retention_daysEach occurrence stores the following PII:
- User email -- only captured when
config.capture_user_email = trueand the user model responds to#email - IP address -- from
request.remote_ip - Custom user context -- anything set via
RailsInformant::Current.user_context
For GDPR compliance, only include identifiers needed for debugging (e.g., user ID) rather than personal data. You can override automatic user detection by setting user context explicitly:
# In a before_action or around_action
RailsInformant::Current.user_context = { id: current_user.id }All stored context passes through ActiveSupport::ParameterFilter, so adding keys to filter_parameters suppresses them:
# config/application.rb
config.filter_parameters += [:email]This replaces email values with [FILTERED] in occurrence data. IP addresses can be suppressed the same way by adding :ip.
- MCP server requires token authentication (
secure_compare) - All stored context is filtered through
ActiveSupport::ParameterFilter - MCP server enforces HTTPS by default
- Security headers:
Cache-Control: no-store,X-Content-Type-Options: nosniff - Error capture never breaks the host application
- Webhook payloads strip PII by default
- Rate limiting -- the engine does not include built-in rate limiting. Add rate limiting on the
/informant/prefix in production, for example with Rack::Attack:
# config/initializers/rack_attack.rb
Rack::Attack.throttle("informant", limit: 60, period: 1.minute) do |req|
req.ip if req.path.start_with?("/informant/")
endThis project is licensed under the MIT License -- see the LICENSE file for details.