When upgrading to a newer version of PostgreSQL, I started noticing random merge conflicts in db/structure.sql
whenever I ran rails db:migrate
. This happens in any Ruby on Rails app that uses SQL schema format instead of the default Ruby-based schema.
The culprit? A new addition in PostgreSQL's structure dumps:
\restrict UwjeW0L2LmcAYzRcF7mQvbj8424RiEhd5GN4cRvjlLTiknOxUKFNjvE5bEz80JQ
\unrestrict UwjeW0L2LmcAYzRcF7mQvbj8424RiEhd5GN4cRvjlLTiknOxUKFNjvE5bEz80JQ
These lines appear at the top and in the middle of the dump file, and the random tokens change every time. After every migration, db/structure.sql
would show meaningless changes like this during a git rebase:
-\restrict UwjeW0L2LmcAYzRcF7mQvbj8424RiEhd5GN4cRvjlLTiknOxUKFNjvE5bEz80JQ
+\restrict TAXaYefQ7OaPsbhTIwM0eA6r8S102Jqiy0mRQfQXQQmIdA9fqI7q4LFmKpchNqQ
Background: Why These Lines Exist
PostgreSQL 17.6 introduced the \restrict
and \unrestrict
meta-commands as a security measure (CVE-2025-8714). These commands prevent malicious psql
meta-commands from being executed if a SQL dump is restored from an untrusted database. When pg_dump
runs, it enters a restricted mode with a random key using \restrict
, and exits it with a matching key via \unrestrict
. The key must match between both lines, and a new random key is generated for every dump.
This mechanism enhances security for production backups and restores. In local development, when running rails db:migrate
on a trusted dev database, these lines serve no purpose. They only add unnecessary noise.
You can learn more from the official documentation and release notes:
The Fix
To prevent this from happening, I added a small Rake task that automatically cleans up those lines after every schema dump.
# lib/tasks/database.rake
# frozen_string_literal: true
# Remove PostgreSQL-specific \unrestrict and \restrict lines from structure.sql
# These lines cause merge conflicts because they contain random tokens that change
# with each dump in newer versions of PostgreSQL
#
# This workaround is only needed for Rails 7.0.x and 7.1.x
# Rails 7.2+ and 8.0+ have this fix built-in (see Rails PR #55510)
namespace :db do
namespace :schema do
desc 'Remove PostgreSQL-specific \unrestrict and \restrict lines from structure.sql'
task :remove_restrict_lines do
# Check Rails version - this task should not be needed for Rails 7.2+
if Rails.gem_version >= Gem::Version.new('7.2.0')
raise 'This task is only needed for Rails 7.0.x and 7.1.x. ' \
'Rails 7.2+ handles this automatically. Please remove this task.'
end
structure_file = 'db/structure.sql'
content = File.read(structure_file)
# Remove lines that start with \unrestrict or \restrict, along with any trailing empty lines
cleaned_content = content.gsub(/^\\(?:un)?restrict\s+.*$\n+/, '')
File.write(structure_file, cleaned_content)
end
end
end
# Run the cleanup task after structure dump
Rake::Task['db:schema:dump'].enhance do
Rake::Task['db:schema:remove_restrict_lines'].invoke
end
Result
Now when I run migrations, Rails automatically removes the noise:
Dumping schema
Removed \unrestrict and \restrict lines from db/structure.sql
And the resulting diff looks clean again:
@@ -3822,6 +3821,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20251007095427'),
('20251007095426'),
('20251007095425'),
+('20251006100600'),
('20250725073614'),
('20250325000001'),
('20250314011832'),
No more random tokens. No more merge conflicts.
Rails Update
This issue has been fixed in Rails itself. The change was merged in Rails PR #55510 by ZimbiX (Brendan Weibrecht) and included in Rails 8.1.0 beta1. The patch was also backported to the 8-0-stable and 7-2-stable branches, and confirmed present in the latest 7.2 releases. Rails now automatically removes the \restrict
and \unrestrict
lines (and the extra blank line after them) when dumping the schema with PostgreSQL 13.22+, 14.19+, 15.14+, 16.10+, 17.6+, and 18 beta 3.
If you are still using Rails 7.0.x or 7.1.x, those versions are no longer maintained. You can use the Rake task shown above as a workaround to clean up your schema dumps.
Summary
PostgreSQL introduced \restrict
and \unrestrict
commands in newer versions of pg_dump
, but they are unnecessary for Rails apps. If your app uses SQL schema dumps, this affects every rails db:migrate
command. Adding a simple post-dump cleanup task keeps your schema diffs clean and your pull requests free from random noise.