Rails Informant
Self-hosted error monitoring for Rails, built for AI agents
Quick Start ◆ Configuration ◆ Noise Suppression ◆ MCP Server ◆ Data and Privacy
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 AI agents can query, triage, and fix production errors directly.
No dashboard. The agent is the interface.
- Agent-native -- 14 MCP tools let AI agents list, inspect, resolve, and fix errors without a browser.
- Self-hosted -- Errors stay in your database. No external service, no data leaving your infrastructure.
- Zero-config capture -- Automatic via
Rails.errorsubscriber and Rack middleware. Breadcrumbs fromActiveSupport::Notificationsprovide structured debugging context. - Lightweight -- Two database tables, no Redis, no background workers beyond ActiveJob.
Quick Start
Add to your Gemfile:
gem "rails-informant"
Install:
bundle install
bin/rails generate rails_informant:install
bin/rails db:migrate
Set an authentication token:
bin/rails credentials:edit
rails_informant:
api_token: your-secret-token # generate with: openssl rand -hex 32
Install Claude Code integration:
bin/rails generate rails_informant:skill
Errors are captured automatically. To capture manually:
RailsInformant.capture(exception, context: { order_id: 42 })
Configuration
# 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
end
Every option can be set via an environment variable. The initializer takes precedence.
| Option | Env var | Default | Description |
|---|---|---|---|
api_token |
INFORMANT_API_TOKEN |
nil |
Authentication token for API/MCP access |
capture_errors |
INFORMANT_CAPTURE_ERRORS |
true |
Enable/disable error capture |
capture_user_email |
(none) | false |
Capture email from detected user (PII -- opt-in) |
ignored_exceptions |
INFORMANT_IGNORED_EXCEPTIONS |
[] |
Exception classes to skip (walks cause chain) |
ignored_paths |
INFORMANT_IGNORED_PATHS |
[] |
Request paths to skip (exact or segment match) |
job_attempt_threshold |
INFORMANT_JOB_ATTEMPT_THRESHOLD |
nil |
Suppress job errors until Nth retry |
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 |
spike_protection |
(none) | nil |
Rate-limit per error group: { threshold: 50, window: 1.minute } |
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.
Noise Suppression
Default Ignored Exceptions
Informant ignores only exceptions that are noise in every context: SignalException
and SystemExit (process control), plus a few client/tamper errors
(Rack::Utils::InvalidParameterError, Mime::Type::InvalidMimeType,
CGI::Session::CookieStore::TamperedWithCookie).
It deliberately does not ignore Rails' HTTP client errors -- RecordNotFound,
RoutingError, ParameterMissing, and the rest of the 4xx family. On the request path
Rails already maps those to responses (404, 422, ...) and never reports them to
Rails.error, so suppressing them again here was redundant. Off the request path -- in a
background job, a rake task, or wrapped as the cause of another error -- those same
exceptions are real bugs, so Informant records them. A job that dies on an
ActiveJob::DeserializationError caused by a missing record now alerts instead of
vanishing silently.
Add your own classes with config.ignored_exceptions; it still walks the cause chain, so
ignoring a class also ignores exceptions that wrap it.
Interactive Console
Informant never captures errors raised in an interactive rails console session -- nothing
is recorded and no notifications are sent. You see exceptions live at the prompt, so
recording and alerting on them would only be noise. This is always on and not configurable.
Errors from web requests, background jobs, and rake tasks are captured as normal.
Silenced Blocks
RailsInformant.silence do
risky_operation_you_dont_care_about
end
Thread-safe via CurrentAttributes. Nesting is supported.
Before Record Callbacks
Hook into the recording pipeline to filter, modify fingerprints, or override severity:
config.before_record do |event|
event.halt! if event..include?("timeout")
event.fingerprint = "stripe-errors" if event.error_class.start_with?("Stripe::")
event.severity = "warning" if event.error_class == "Net::ReadTimeout"
end
The event exposes: error, error_class, message, severity, controller_action, job_class, request_path, fingerprint. Callbacks that raise are logged and skipped.
Custom Exception Context
Exceptions implementing to_informant_context have their context merged into occurrences automatically:
class PaymentError < StandardError
def to_informant_context
{ payment_id:, gateway: }
end
end
Deploy Auto-Resolve
Notify Informant of a deploy to auto-resolve stale errors (not seen in the last hour):
curl -X POST https://myapp.com/informant/api/v1/deploy \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"sha": "abc1234"}'
Resolved errors automatically reopen on regression. Also available as the notify_deploy MCP tool.
MCP Server
The bundled informant-mcp executable connects Claude Code to your error data via Model Context Protocol.
Setup
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).
For multi-environment setups, add env vars for each environment:
export INFORMANT_PRODUCTION_URL=https://myapp.com
export INFORMANT_PRODUCTION_TOKEN=<token>
export INFORMANT_STAGING_URL=https://staging.myapp.com
export INFORMANT_STAGING_TOKEN=<token>
Tools
| Tool | Description |
|---|---|
annotate_error |
Add investigation notes |
delete_error |
Delete group and occurrences |
get_error |
Full error detail with recent occurrences |
get_informant_status |
Summary with counts and top errors |
ignore_error |
Mark as ignored |
list_environments |
List configured environments |
list_errors |
List error groups with filtering and pagination |
list_occurrences |
List occurrences with filtering |
mark_duplicate |
Mark as duplicate of another group |
mark_fix_pending |
Mark with fix SHA for auto-resolve on deploy |
notify_deploy |
Notify of a deploy to auto-resolve stale errors |
reopen_error |
Reopen a resolved/ignored error |
resolve_error |
Mark as resolved |
verify_pending_fixes |
Check deployed fixes and auto-resolve verified ones |
Local Development
The MCP server enforces HTTPS by default. For local HTTP URLs, pass --allow-insecure:
{
"mcpServers": {
"informant": {
"command": "informant-mcp",
"args": ["--allow-insecure"]
}
}
}
Architecture
Development Machine Remote Servers
+-----------------------+ +-----------------------+
| Claude Code | | Production |
| | | | /informant |
| | stdio | +-----------------------+
| v | HTTPS+Token
| MCP Server | -----------> +-----------------------+
| (exe/informant-mcp) | | Staging |
| | | /informant |
+-----------------------+ +-----------------------+
Error Group Lifecycle
unresolved --> fix_pending --> resolved (auto, on deploy)
unresolved --> resolved (manual)
unresolved --> ignored
unresolved --> duplicate
resolved --> unresolved [REGRESSION]
Data and Privacy
Occurrences store: user ID (always), email (opt-in via capture_user_email), IP address, and custom context. All context passes through ActiveSupport::ParameterFilter -- add keys to filter_parameters to suppress them.
RailsInformant::Current.user_context = { id: current_user.id }
Security
- Token authentication (
secure_compare), HTTPS enforced by default - All context filtered through
ActiveSupport::ParameterFilter - Security headers:
Cache-Control: no-store,X-Content-Type-Options: nosniff - Error capture never breaks the host application
- No built-in rate limiting -- use Rack::Attack on
/informant/
License
MIT License -- see LICENSE.