Rails の SafeBuffer に ブロック付きの gsub を呼び、$1 を参照したところ、nil でした。
同じ gsub を String で使うと、$1 には値が入ります。

SafeBuffer のソースを見ると、to_str した後に、gsub を呼んでいましたので、
該当部分を取り出し、同じ挙動を再現しました(コードは末尾にあります)。

主要部分は下記です。

  def gsub(*args, &block)
    to_str.gsub(*args, &block)
  end

メソッドの中で、to_str してから gsub を呼ぶことで挙動が変わる理由はなんでしょうか?

$1 が これらの変数はローカルスコープかつスレッドローカルとあるため、
この現象はスコープが異なる、ということなのでしょうか?
その場合、レシーバーはどこですか?

コード
aタグ の text を抜き出すコードです

HREF_RE = /<a[^>]+>([^<]+)<\/a>/ix

def pick_url(text)
  text.gsub(HREF_RE) do |a_tag|
    $1
  end
end

class StringWrapper < String
  def gsub(*args, &block)
    to_str.gsub(*args, &block)
  end
end

require 'minitest/unit'
require 'minitest/autorun'
class ATest < MiniTest::Unit::TestCase
  def setup
    @url = 'http://example.com'
    @html = "<a href='#{@url}'>#{@url}</a>"
    @wrapped = StringWrapper.new(@html)
  end

  def test_gsub
    assert_equal @url, pick_url(@html)
  end

  def test_wrapped_gsub
    assert_equal @url, pick_url(@wrapped)
  end

  def test_wrapped_to_str_gsub
    assert_equal @url, pick_url(@wrapped.to_str)
  end
end

結果は下記になります。

  1) Failure:
ATest#test_wrapped_gsub [gsub_text_01.rb:30]:
Expected: "http://example.com"
  Actual: ""

3 runs, 3 assertions, 1 failures, 0 errors, 0 skips

追記 2015-07-20

参考資料
- How to fix gsub on SafeBuffer objects - makandropedia
- gsub_file with block, no access to $1 etc? · Issue #207 · erikhuda/thor
- ruby - Using $1, $2, etc. global variables inside method definition - Stack Overflow