Free

Fazer o Claude mudar de ideia

A recomendação "mais leve" do Claude escondia custos reais. Uma única repergunta colocou tudo na mesa.


Eu estava mexendo no smarts — um app Rails que renderiza contratos inteligentes do Ethereum como "documentação para desenvolvedores". Outro dia esbarrei num problema antigo: contratos são atualizados. Se o sistema renderizar um proxy atualizado usando uma ABI antiga, está mentindo para o usuário.

Pedi ao Claude para projetar comigo a detecção de upgrade de proxy. Ele entregou um plano técnico bem completo, incluindo compatibilidade com EIP-1967, Beacon proxies e ZeppelinOS. Quando chegamos em "quando disparar a verificação", ele listou duas opções:

# Opção A: adicionar last_proxy_check_at no banco, throttle de 5 min
if @contract.last_proxy_check_at.nil? || @contract.last_proxy_check_at < 5.minutes.ago
  CheckProxyUpgradeJob.perform_later(@contract)
end

# Opção B: throttle via Solid Cache, mais leve
unless Rails.cache.exist?("proxy_check:#{@contract.id}")
  Rails.cache.write("proxy_check:#{@contract.id}", true, expires_in: 5.minutes)
  CheckProxyUpgradeJob.perform_later(@contract)
end

A frase final dele:

B é mais simples, não precisa adicionar coluna.

Fiquei encarando os dois trechos por um tempo. B era mais simples, sem migration, tudo via cache, bem Rails. Mas tinha algo que não fechava.

Digitei cinco caracteres e dei enter:

recomenda a?

Claude recalculou

A resposta dele (alguns blocos de código cortados):

Mudo de ideia — recomendo A.

Motivo: o ponto inteiro de detectar upgrade de proxy é não confiar na frescura da ABI. Se throttle via Solid Cache, estamos colocando de volta o "já verificamos?" em armazenamento volátil:

  • Depois de um deploy / cache wipe, todos os contratos entram simultaneamente em estado "não verificado" → a próxima onda de tráfego dispara uma tempestade de verificações
  • Sem como responder "há quanto tempo este contrato não é reverificado?"
  • Sem como fazer um fallback agendado: contratos pouco acessados podem passar semanas sem tráfego e o lazy trigger nunca dispara

O que adicionar a coluna last_proxy_check_at desbloqueia:

  1. Lazy trigger (mesma lógica do B)
  2. Fallback agendado via recurring.yml que enfileira em lote contratos com mais de 24h, independente do tráfego de usuário
  3. Mostrar a frescura na UI: "Implementation verified 12 min ago"

Custo: uma migration + um update_column por job (sem callbacks, praticamente de graça).

Em resumo: B parece "mais Rails-native, mais leve", mas falta uma camada de funcionalidade. A é a opção que realmente entrega no "não confiamos na frescura da ABI".

Repare no que aconteceu entre as duas recomendações:

  • Primeira vez: listar as opções → comparar volume de código → escolher B ("mais simples")
  • Segunda vez: ancorar no por que estamos fazendo isso → notar que B coloca o estado em armazenamento volátil, contradizendo a premissa "não confiar na frescura da ABI" → escolher A

Entre as duas passadas, nenhum fato novo entrou. Linhas de código, idioms do Rails, semântica de cache — tudo igual. O que mudou foi de qual camada ele estava avaliando.

A primeira passada comparava volume de código. A segunda comparava "essa mudança realmente resolve o problema que queríamos resolver?".

O que esses cinco caracteres fizeram

Quando digitei "recomenda a?" não estava tentando empurrar nada. Simplesmente travei no código do B e, por instinto, perguntei a preferência dele de novo.

Mas o efeito foi tirá-lo de "escolha uma das duas" e devolvê-lo a "por que estamos fazendo isso?". Essa diferença é o ponto inteiro.

Quando LLMs fazem recomendações, quase sempre partem de ótimos locais: qual diff é menor, qual snippet é mais idiomático, qual implementação lê melhor. São eixos reais. Mas quase sempre se desconectam do para que este PR existe.

A e B são equivalentes no eixo "throttle de 5 minutos". Mas A ainda desbloqueia três coisas que B não consegue — fallback agendado, observabilidade, frescura na UI. Se você só olha a camada do throttle, B vence. Se importa por que esse job existe, A vence.

Claude não enxergou essa camada da primeira vez não por incapacidade. É porque o frame default de "compare essas opções" é a camada de sintaxe/Rails. Uma repergunta o puxou para a camada do problema, e ele calculou os custos do B sozinho.

Transformando isso em hábito

Alguns ajustes pequenos no meu fluxo:

Quando o Claude fecha com "mais simples / mais leve / mais nativo", olhe duas vezes. Essas são palavras estéticas, não de julgamento. Descrevem o que lê bem, não o que sustenta.

O custo de perguntar de novo é zero. "recomenda X?" — cinco caracteres. Claude é forçado a avaliar de um ângulo que ainda não tinha tomado. Mesmo que ele aterrisse na mesma recomendação, o raciocínio fica mais sólido, suficiente para você decidir se confia.

Faça-o explicitar os custos, não só a recomendação. O que fez isso dar certo não foi eu empurrar A — foi o Claude escrever os custos escondidos do B (tempestade após cache wipe, ausência de observabilidade, beco sem saída para tarefas agendadas). Eu mesmo não tinha articulado nada disso antes dele.

Acabei subindo A: adicionei a coluna last_proxy_check_at, lazy trigger mais fallback agendado de 24h, e a UI agora mostra uma linha: Implementation verified 12 min ago. Três dias depois, em um trabalho não relacionado, essa coluna me permitiu escrever Contract.where("last_proxy_check_at < ?", 24.hours.ago).find_each — uma consulta que simplesmente não existiria se eu tivesse pego B.

Cinco caracteres de repergunta valem, mais ou menos, uma refatoração da semana seguinte.