From f1c32f6a11077cfcef9ac968bbea79fb033e2b2b Mon Sep 17 00:00:00 2001 From: Shlee Date: Wed, 28 Jan 2026 20:44:03 +1030 Subject: [PATCH 01/10] Unclosed connection leak when replacing pooled connection in SharedTimedStack.try_create (#37335) --- app/lib/connection_pool/shared_timed_stack.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/lib/connection_pool/shared_timed_stack.rb b/app/lib/connection_pool/shared_timed_stack.rb index 14a5285c45..f1ace33017 100644 --- a/app/lib/connection_pool/shared_timed_stack.rb +++ b/app/lib/connection_pool/shared_timed_stack.rb @@ -71,6 +71,7 @@ class ConnectionPool::SharedTimedStack throw_away_connection = @queue.pop @tagged_queue[throw_away_connection.site].delete(throw_away_connection) @create_block.call(preferred_tag) + throw_away_connection.close elsif @created != @max connection = @create_block.call(preferred_tag) @created += 1 From dcc5c2b6f6e1012b518c79d2737251b8a6954215 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 21 Jan 2026 13:02:41 +0100 Subject: [PATCH 02/10] Fix cross-server conversation tracking (#37559) --- app/lib/activitypub/activity/create.rb | 1 + app/lib/activitypub/tag_manager.rb | 16 +++++++----- app/lib/ostatus/tag_manager.rb | 10 +++----- spec/lib/activitypub/activity/create_spec.rb | 26 +++++++++++++++++++- spec/lib/activitypub/tag_manager_spec.rb | 8 ------ 5 files changed, 39 insertions(+), 22 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 43c7bb1fe7..a7d2be35ed 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -379,6 +379,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def conversation_from_uri(uri) return nil if uri.nil? return Conversation.find_by(id: OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Conversation')) if OStatus::TagManager.instance.local_id?(uri) + return ActivityPub::TagManager.instance.uri_to_resource(uri, Conversation) if ActivityPub::TagManager.instance.local_uri?(uri) begin Conversation.find_or_create_by!(uri: uri) diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index 3174d1792e..40adb57378 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -241,12 +241,6 @@ class ActivityPub::TagManager !host.nil? && (::TagManager.instance.local_domain?(host) || ::TagManager.instance.web_domain?(host)) end - def uri_to_local_id(uri, param = :id) - path_params = Rails.application.routes.recognize_path(uri) - path_params[:username] = Rails.configuration.x.local_domain if path_params[:controller] == 'instance_actors' - path_params[param] - end - def uris_to_local_accounts(uris) usernames = [] ids = [] @@ -264,6 +258,14 @@ class ActivityPub::TagManager uri_to_resource(uri, Account) end + def uri_to_local_conversation(uri) + path_params = Rails.application.routes.recognize_path(uri) + return unless path_params[:controller] == 'activitypub/contexts' + + account_id, conversation_id = path_params[:id].split('-') + Conversation.find_by(parent_account_id: account_id, id: conversation_id) + end + def uri_to_resource(uri, klass) return if uri.nil? @@ -271,6 +273,8 @@ class ActivityPub::TagManager case klass.name when 'Account' uris_to_local_accounts([uri]).first + when 'Conversation' + uri_to_local_conversation(uri) else StatusFinder.new(uri).status end diff --git a/app/lib/ostatus/tag_manager.rb b/app/lib/ostatus/tag_manager.rb index cb0c9f8966..7d0f23c4dc 100644 --- a/app/lib/ostatus/tag_manager.rb +++ b/app/lib/ostatus/tag_manager.rb @@ -11,16 +11,12 @@ class OStatus::TagManager def unique_tag_to_local_id(tag, expected_type) return nil unless local_id?(tag) - if ActivityPub::TagManager.instance.local_uri?(tag) - ActivityPub::TagManager.instance.uri_to_local_id(tag) - else - matches = Regexp.new("objectId=([\\d]+):objectType=#{expected_type}").match(tag) - matches[1] unless matches.nil? - end + matches = Regexp.new("objectId=([\\d]+):objectType=#{expected_type}").match(tag) + matches[1] unless matches.nil? end def local_id?(id) - id.start_with?("tag:#{Rails.configuration.x.local_domain}") || ActivityPub::TagManager.instance.local_uri?(id) + id.start_with?("tag:#{Rails.configuration.x.local_domain}") end def uri_for(target) diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index 1e8a2a29db..19b6014af1 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -471,7 +471,7 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'with a reply' do + context 'with a reply without explicitly setting a conversation' do let(:original_status) { Fabricate(:status) } let(:object_json) do @@ -493,6 +493,30 @@ RSpec.describe ActivityPub::Activity::Create do end end + context 'with a reply explicitly setting a conversation' do + let(:original_status) { Fabricate(:status) } + + let(:object_json) do + build_object( + inReplyTo: ActivityPub::TagManager.instance.uri_for(original_status), + conversation: ActivityPub::TagManager.instance.uri_for(original_status.conversation), + context: ActivityPub::TagManager.instance.uri_for(original_status.conversation) + ) + end + + it 'creates status' do + expect { subject.perform }.to change(sender.statuses, :count).by(1) + + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.thread).to eq original_status + expect(status.reply?).to be true + expect(status.in_reply_to_account).to eq original_status.account + expect(status.conversation).to eq original_status.conversation + end + end + context 'with mentions' do let(:recipient) { Fabricate(:account) } diff --git a/spec/lib/activitypub/tag_manager_spec.rb b/spec/lib/activitypub/tag_manager_spec.rb index 6cbb58055e..7571d03f04 100644 --- a/spec/lib/activitypub/tag_manager_spec.rb +++ b/spec/lib/activitypub/tag_manager_spec.rb @@ -612,14 +612,6 @@ RSpec.describe ActivityPub::TagManager do end end - describe '#uri_to_local_id' do - let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } - - it 'returns the local ID' do - expect(subject.uri_to_local_id(subject.uri_for(account), :username)).to eq account.username - end - end - describe '#uris_to_local_accounts' do it 'returns the expected local accounts' do account = Fabricate(:account) From 8935137526c775b7d8e2d32002418f41d2c39570 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 22 Jan 2026 13:38:00 +0100 Subject: [PATCH 03/10] Shorten caching of quote posts pending approval (#37570) --- app/controllers/statuses_controller.rb | 2 +- app/models/quote.rb | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index e673faca04..65db807d18 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -29,7 +29,7 @@ class StatusesController < ApplicationController end format.json do - expires_in 3.minutes, public: true if @status.distributable? && public_fetch_mode? + expires_in @status.quote&.pending? ? 5.seconds : 3.minutes, public: true if @status.distributable? && public_fetch_mode? render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter end end diff --git a/app/models/quote.rb b/app/models/quote.rb index 4ad393e3a5..425cf63055 100644 --- a/app/models/quote.rb +++ b/app/models/quote.rb @@ -47,6 +47,8 @@ class Quote < ApplicationRecord def accept! update!(state: :accepted) + + reset_parent_cache! if attribute_changed?(:state) end def reject! @@ -75,6 +77,15 @@ class Quote < ApplicationRecord private + def reset_parent_cache! + return if status_id.nil? + + Rails.cache.delete("v3:statuses/#{status_id}") + + # This clears the web cache for the ActivityPub representation + Rails.cache.delete("statuses/show:v3:statuses/#{status_id}") + end + def set_accounts self.account = status.account self.quoted_account = quoted_status&.account From 81716f7e277174375d5c9c0761b0aa36ea2827db Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 23 Jan 2026 14:35:43 +0100 Subject: [PATCH 04/10] Fix quote cache invalidation (#37592) --- app/models/quote.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/quote.rb b/app/models/quote.rb index 425cf63055..e4f3b823fe 100644 --- a/app/models/quote.rb +++ b/app/models/quote.rb @@ -48,7 +48,7 @@ class Quote < ApplicationRecord def accept! update!(state: :accepted) - reset_parent_cache! if attribute_changed?(:state) + reset_parent_cache! if attribute_previously_changed?(:state) end def reject! From 569ff6c8ad5024d181c531a28afc35b925f6467b Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 27 Jan 2026 17:01:22 +0100 Subject: [PATCH 05/10] Fix error when encountering invalid tag in updated object (#37635) --- app/services/activitypub/process_status_update_service.rb | 6 +++++- .../activitypub/process_status_update_service_spec.rb | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 1cdf0b4830..c45454fc7f 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -204,7 +204,11 @@ class ActivityPub::ProcessStatusUpdateService < BaseService def update_tags! previous_tags = @status.tags.to_a - current_tags = @status.tags = Tag.find_or_create_by_names(@raw_tags) + current_tags = @status.tags = @raw_tags.flat_map do |tag| + Tag.find_or_create_by_names([tag]).filter(&:valid?) + rescue ActiveRecord::RecordInvalid + [] + end return unless @status.distributable? diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb index 9d63c5f1fe..9dc1ece33e 100644 --- a/spec/services/activitypub/process_status_update_service_spec.rb +++ b/spec/services/activitypub/process_status_update_service_spec.rb @@ -258,6 +258,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do tag: [ { type: 'Hashtag', name: 'foo' }, { type: 'Hashtag', name: 'bar' }, + { type: 'Hashtag', name: '#2024' }, ], } end From 4c1fbe4e2e81d5648c25627d6f863796977065f0 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 29 Jan 2026 10:38:57 +0100 Subject: [PATCH 06/10] Fix followers with profile subscription (bell icon) being notified of post edits (#37646) --- app/workers/feed_insert_worker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/feed_insert_worker.rb b/app/workers/feed_insert_worker.rb index e883daf3ea..b0c9c9181f 100644 --- a/app/workers/feed_insert_worker.rb +++ b/app/workers/feed_insert_worker.rb @@ -53,7 +53,7 @@ class FeedInsertWorker def notify?(filter_result) return false if @type != :home || @status.reblog? || (@status.reply? && @status.in_reply_to_account_id != @status.account_id) || - filter_result == :filter + update? || filter_result == :filter Follow.find_by(account: @follower, target_account: @status.account)&.notify? end From 1ba2b1cdc107a7c0d10e7684cad019cb96315d08 Mon Sep 17 00:00:00 2001 From: PGray <77597544+PGrayCS@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:33:04 +0000 Subject: [PATCH 07/10] Fix quote cancel button not appearing after edit then delete-and-redraft (#37066) --- app/javascript/mastodon/components/status.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 2a8c9bfb2d..aac58b31f4 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -145,6 +145,7 @@ class Status extends ImmutablePureComponent { 'hidden', 'unread', 'pictureInPicture', + 'onQuoteCancel', ]; state = { From ff20ce9acf9d4974b5a91d1161a0b7e589fa50d1 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 30 Jan 2026 10:15:22 +0100 Subject: [PATCH 08/10] Clear affected relationship cache on Move activities (#37664) --- app/workers/move_worker.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/workers/move_worker.rb b/app/workers/move_worker.rb index 1a5745a86a..d0103dc664 100644 --- a/app/workers/move_worker.rb +++ b/app/workers/move_worker.rb @@ -64,6 +64,16 @@ class MoveWorker .in_batches do |follows| ListAccount.where(follow: follows).in_batches.update_all(account_id: @target_account.id) num_moved += follows.update_all(target_account_id: @target_account.id) + + # Clear any relationship cache, since callbacks are not called + Rails.cache.delete_multi(follows.flat_map do |follow| + [ + ['relationship', follow.account_id, follow.target_account_id], + ['relationship', follow.target_account_id, follow.account_id], + ['relationship', follow.account_id, @target_account.id], + ['relationship', @target_account.id, follow.account_id], + ] + end) end num_moved From 68a26ce7c650ac2140c295b52f78b3d08191d6bd Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 30 Jan 2026 10:59:04 +0100 Subject: [PATCH 09/10] Fix connection recycling pushing symbols to connection pool (#37674) --- app/lib/connection_pool/shared_timed_stack.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/connection_pool/shared_timed_stack.rb b/app/lib/connection_pool/shared_timed_stack.rb index f1ace33017..8a13f4473b 100644 --- a/app/lib/connection_pool/shared_timed_stack.rb +++ b/app/lib/connection_pool/shared_timed_stack.rb @@ -70,8 +70,8 @@ class ConnectionPool::SharedTimedStack if @created == @max && !@queue.empty? throw_away_connection = @queue.pop @tagged_queue[throw_away_connection.site].delete(throw_away_connection) - @create_block.call(preferred_tag) throw_away_connection.close + @create_block.call(preferred_tag) elsif @created != @max connection = @create_block.call(preferred_tag) @created += 1 From 527bed86b5b89b48e21bbb6c8c18407d7d6cb82c Mon Sep 17 00:00:00 2001 From: PGray <77597544+PGrayCS@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:33:04 +0000 Subject: [PATCH 10/10] [Glitch] Fix quote cancel button not appearing after edit then delete-and-redraft Port f1c00feb5c98389d18243494514249c536b5ce7c to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/components/status.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index 98d1227c86..31df0d8c5d 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -159,6 +159,7 @@ class Status extends ImmutablePureComponent { 'expanded', 'unread', 'pictureInPicture', + 'onQuoteCancel', 'previousId', 'nextInReplyToId', 'rootId',