Introduction
ActiveJob uses GlobalID to serialize ActiveRecord model references in job arguments. Instead of serializing the entire object, GlobalID stores a URI-like string (gid://app/User/123) that is resolved back to the model when the job executes. When GlobalID is misconfigured, the model does not include GlobalID::Identification, or the record is deleted before the job runs, deserialization fails with GlobalID::Errors::MissingURIError or ActiveJob::DeserializationError.
Symptoms
GlobalID::Errors::MissingURIError: Unable to serialize without a valid GlobalIDActiveJob::DeserializationError: Error while trying to deserialize arguments: Couldn't find User with 'id'=123- Job arguments contain raw hashes instead of GlobalID references
- Custom model classes missing
include GlobalID::Identification - Jobs enqueued with deleted records fail on execution
Error in Sidekiq logs:
``
2026-04-09T10:15:00.000Z pid=1234 class=SendWelcomeEmailJob jid=abc123
ERROR: ActiveJob::DeserializationError: Error while trying to deserialize arguments:
Couldn't find User with 'id'=456
GlobalID::Errors::MissingURIError: Unable to find a valid URI for #<User:0x00007f>
Common Causes
- Model does not have an
idcolumn or primary key set GlobalID::Identificationnot included in non-ActiveRecord models- Record deleted between job enqueue and execution
- Custom
to_global_idmethod returning invalid URI - Multi-database setup where GlobalID app name does not match
Step-by-Step Fix
- 1.Ensure model includes GlobalID::Identification:
- 2.```ruby
- 3.# For non-ActiveRecord models
- 4.class ExternalService
- 5.include GlobalID::Identification
attr_accessor :id, :name
def initialize(id:, name:) @id = id @name = name end
# Required by GlobalID def self.find(id) # Custom lookup logic ExternalService.new(id: id, name: "Service #{id}") end end ```
- 1.Handle deleted records gracefully in jobs:
- 2.```ruby
- 3.class SendWelcomeEmailJob < ApplicationJob
- 4.retry_on ActiveJob::DeserializationError, wait: :exponentially_long, attempts: 3
discard_on ActiveJob::DeserializationError do |job, error| Rails.logger.warn( "Discarding #{job.class} - record no longer exists: #{error.message}" ) end
def perform(user) # user is deserialized from GlobalID # If record was deleted, ActiveJob::DeserializationError is raised UserMailer.welcome(user).deliver_later rescue ActiveJob::DeserializationError Rails.logger.info("User was deleted before welcome email could be sent") end end ```
- 1.Configure GlobalID app identifier for multi-database setups:
- 2.```ruby
- 3.# config/initializers/global_id.rb
- 4.GlobalID.app = Rails.application.class.module_parent_name.downcase
# For multi-database, specify the database in GlobalID class User < ApplicationRecord self.global_id_model_name = "primary_db/user"
def to_global_id(options = {}) super(options.merge(app: "primary_app")) end end ```
- 1.Pass ID directly instead of relying on GlobalID:
- 2.```ruby
- 3.# When GlobalID is unreliable, pass ID explicitly
- 4.class NotifyUserJob < ApplicationJob
- 5.# Accept ID instead of object
- 6.def perform(user_id)
- 7.user = User.find_by(id: user_id)
- 8.return if user.nil? # Gracefully handle deleted records
NotificationService.call(user) end end
# Enqueue with ID NotifyUserJob.perform_later(user.id)
# Or use rescue_from in ApplicationJob class ApplicationJob < ActiveJob::Base rescue_from ActiveRecord::RecordNotFound do Rails.logger.warn("Record not found for job #{self.class}") end end ```
- 1.Debug GlobalID serialization issues:
- 2.```ruby
- 3.# In Rails console
- 4.user = User.first
# Check if GlobalID works user.to_global_id # => #<GlobalID:0x00007f @uri=#<URI::GID gid://myapp/User/1>>
# Verify model responds to required methods user.respond_to?(:id) # => true user.respond_to?(:model_name) # => true user.class.respond_to?(:find) # => true
# Test round-trip gid = user.to_global_id GlobalID::Locator.locate(gid) # => #<User id: 1, ...> ```
Prevention
- Always check if record exists at job execution time, not just at enqueue
- Use
discard_on ActiveJob::DeserializationErrorfor non-critical jobs - Prefer passing IDs over model objects for jobs that outlive the request
- Add GlobalID validation to model specs:
expect(user.to_global_id).to be_valid - Monitor DeserializationError rates in error tracking service
- Document which jobs require GlobalID and which use plain IDs