Gsub produzindo resultados diferentes executando o mesmo código

Variáveis como $1 são preenchidas após a execução de uma correspondência de regex. Simplificando um pouco o seu exemplo:

s.gsub('original(value)', "replacement#{$1}")

A expressão "replacement#{$1}" é avaliada antes de a função gsub ser chamada. Portanto, $1 conterá o valor residual de alguma tarefa de regex anterior. (É por isso que sua segunda tentativa funciona — ela usa o $1 da primeira tentativa)

Existem algumas opções para resolver esse problema. gsub possui diversas funcionalidades.

Minha preferência é passar um bloco para o gsub. O bloco é avaliado após a correspondência do regex, então $1 funcionará como você espera:

s.gsub('original(value)') { |match| "replacement#{$1}" }

Ou você pode usar o recurso de “backreference” do gsub. Não sou fã dessa sintaxe, mas ela funciona. Em vez de usar "replacement#{$1}", use 'replacement\1' (ou \2, \3, etc.)

s.gsub('original(value)', 'replacement\1')