How It Works Features Pricing Blog
Log In Start Free Trial
Rails · Ruby

Fix ActiveRecord::RecordNotFound: Couldn't find User with 'id'=999 in Rails

This error occurs when ActiveRecord's find method is called with an ID that does not exist in the database. Rails raises RecordNotFound which renders a 404 in production but shows a stack trace in development. Fix it by rescuing the exception in the controller or using find_by with a nil check for custom error handling.

Reading the Stack Trace

ActiveRecord::RecordNotFound (Couldn't find User with 'id'=999): app/controllers/users_controller.rb:8:in `show' actionpack (7.1.3) lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action' actionpack (7.1.3) lib/abstract_controller/base.rb:225:in `process_action' actionpack (7.1.3) lib/action_controller/metal/rendering.rb:165:in `process_action' actionpack (7.1.3) lib/abstract_controller/callbacks.rb:234:in `block in process_action' activesupport (7.1.3) lib/active_support/callbacks.rb:118:in `block in run_callbacks' activesupport (7.1.3) lib/active_support/callbacks.rb:127:in `run_callbacks' actionpack (7.1.3) lib/abstract_controller/callbacks.rb:233:in `process_action' actionpack (7.1.3) lib/action_controller/metal/rescue.rb:22:in `process_action'

Here's what each line means:

Common Causes

1. Deleted or non-existent record

A user was deleted but links to their profile still exist, or a user manually enters an invalid ID in the URL.

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
  end
end

2. Stale cache or bookmark

A cached page or bookmarked URL references a record that has since been deleted from the database.

# URL: /users/999 (bookmarked months ago, user was deleted since)
class UsersController < ApplicationController
  def show
    @user = User.find(params[:id]) # Raises RecordNotFound for deleted user
  end
end

The Fix

Add a rescue_from handler in ApplicationController that catches RecordNotFound and renders a proper 404 page. This handles the error gracefully across all controllers without modifying each action individually.

Before (broken)
class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
  end
end
After (fixed)
class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, with: :record_not_found

  private

  def record_not_found
    render file: Rails.public_path.join('404.html'), status: :not_found, layout: false
  end
end

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
  end
end

Testing the Fix

require 'rails_helper'

RSpec.describe UsersController, type: :controller do
  describe 'GET #show' do
    context 'when the user exists' do
      let(:user) { User.create!(name: 'Alice', email: 'alice@example.com') }

      it 'returns 200' do
        get :show, params: { id: user.id }
        expect(response).to have_http_status(:ok)
      end

      it 'assigns the user' do
        get :show, params: { id: user.id }
        expect(assigns(:user)).to eq(user)
      end
    end

    context 'when the user does not exist' do
      it 'returns 404' do
        get :show, params: { id: 999 }
        expect(response).to have_http_status(:not_found)
      end
    end
  end
end

Run your tests:

rspec

Pushing Through CI/CD

git checkout -b fix/record-not-found-handler,git add app/controllers/application_controller.rb spec/controllers/users_controller_spec.rb,git commit -m "fix: add global rescue_from for RecordNotFound to return 404",git push origin fix/record-not-found-handler

Your CI config should look something like this:

name: CI
on:
  pull_request:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_DB: test_db
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true
      - run: bundle exec rails db:create db:migrate
        env:
          DATABASE_URL: postgres://postgres:postgres@localhost:5432/test_db
          RAILS_ENV: test
      - run: bundle exec rspec
        env:
          DATABASE_URL: postgres://postgres:postgres@localhost:5432/test_db
          RAILS_ENV: test

The Full Manual Process: 18 Steps

Here's every step you just went through to fix this one bug:

  1. Notice the error alert or see it in your monitoring tool
  2. Open the error dashboard and read the stack trace
  3. Identify the file and line number from the stack trace
  4. Open your IDE and navigate to the file
  5. Read the surrounding code to understand context
  6. Reproduce the error locally
  7. Identify the root cause
  8. Write the fix
  9. Run the test suite locally
  10. Fix any failing tests
  11. Write new tests covering the edge case
  12. Run the full test suite again
  13. Create a new git branch
  14. Commit and push your changes
  15. Open a pull request
  16. Wait for code review
  17. Merge and deploy to production
  18. Monitor production to confirm the error is resolved

Total time: 30-60 minutes. For one bug.

Or Let bugstack Fix It in Under 2 minutes

Every step above? bugstack does it automatically.

Step 1: Install the SDK

gem install bugstack

Step 2: Initialize

require 'bugstack'

Bugstack.init(api_key: ENV['BUGSTACK_API_KEY'])

Step 3: There is no step 3.

bugstack handles everything from here:

  1. Captures the stack trace and request context
  2. Pulls the relevant source files from your GitHub repo
  3. Analyzes the error and understands the code context
  4. Generates a minimal, verified fix
  5. Runs your existing test suite
  6. Pushes through your CI/CD pipeline
  7. Deploys to production (or opens a PR for review)

Time from error to fix deployed: Under 2 minutes.

Human involvement: zero.

Try bugstack Free →

No credit card. 5-minute setup. Cancel anytime.

Deploying the Fix (Manual Path)

  1. Run rspec locally to confirm 404 responses for missing records.
  2. Open a pull request with the rescue_from handler.
  3. Wait for CI checks to pass on the PR.
  4. Have a teammate review and approve the PR.
  5. Merge to main and verify the 404 page renders in staging.

Frequently Asked Questions

BugStack runs specs for all controllers, verifies RecordNotFound returns 404 consistently, and checks that valid records still load correctly.

All fixes are submitted as pull requests with full CI checks. Your team reviews the rescue_from logic before merging.

Yes, Rails shows a 404 page in production by default. But adding rescue_from gives you control over the response format, which is important for JSON APIs.

For APIs, find with rescue_from is cleaner because it centralizes error handling. find_by with manual nil checks works but adds boilerplate to every action.