Erros de deploy não dão sintoma. Quatro guardrails — vaults separados, scripts EDITOR, verificação por releitura, switch em runtime por env — pra usar o Claude sem queimar tudo.
A grande diferença entre deploy e escrever código: deploy é one-shot, alto risco, e doloroso de reverter. Escreve uma linha quebrada, um test roda e pega. Escreve uma linha de credentials quebrada, só vai saber no primeiro pagamento real — usuários já passaram o cartão, o dinheiro não caiu na sua conta, os logs são só 400.
Recentemente levei o how2claude de dev local para produção com o Claude: conta Stripe live, wallet x402 mainnet, Google OAuth, Kamal secrets. O Claude não sabe quais valores são de teste e quais são reais. Não sabe que sk_live_ vs sk_test_ é uma diferença de uma letra com consequências apocalípticas. As guardrails você coloca.
Rails por padrão usa config/credentials.yml.enc, descriptografado por config/master.key. Se você usa esse default, já perdeu.
Se você coloca sk_live_xxx nesse arquivo e aí roda testes localmente, seu código de teste está usando Stripe de produção. Cada rodada cobra um cartão real.
Separa em dois:
- config/credentials.yml.enc + config/master.key: dev/test, guarda sk_test_xxx
- config/credentials/production.yml.enc + config/credentials/production.key: prod, guarda sk_live_xxx
Ambos .key no .gitignore, ambos .enc commitados. production.key vive só na máquina de deploy.
Aí Kamal secrets tem que apontar pro arquivo certo:
# .kamal/secrets
-RAILS_MASTER_KEY=$(cat config/master.key)
+RAILS_MASTER_KEY=$(cat config/credentials/production.key)
Dentro do container o RAILS_MASTER_KEY agora aponta pra chave de produção, e o que é descriptografado são credentials de produção. Vi o Claude escrever essa linha — o template default do Kamal é config/master.key (o de dev), que silenciosamente faz deploy do Stripe key de dev pra produção.
bin/rails credentials:edit --environment production abre um editor interativo. O Claude não consegue dirigir editor interativo. Você também não quer colar manualmente uma dúzia de secrets (um typo e arrebenta tudo).
Use esse padrão:
EDITOR="ruby script/set_prod_webhook_secret.rb" \
bin/rails credentials:edit --environment production
rm script/set_prod_webhook_secret.rb
script/set_prod_webhook_secret.rb fica assim:
# ARGV[0] é o path do arquivo YAML temporário descriptografado
file = ARGV[0]
require "yaml"
data = YAML.load_file(file) || {}
data["stripe"] ||= {}
data["stripe"]["webhook_secret"] = "whsec_GHWObNAKFh2HPOlJpbGmlYfIiKz1C8EY"
File.write(file, data.to_yaml)
O Rails escreve o YAML descriptografado num arquivo temporário, chama seu "EDITOR" com aquele path como argumento, e re-encripta na saída. Seu "EDITOR" é só um script Ruby que modifica cirurgicamente uma chave e salva.
Por que isso é bom:
- Preciso: só toca stripe.webhook_secret, nada mais.
- Idempotente: rodar duas vezes é igual a rodar uma.
- Auditável: o script é o diff. Claude escreve, você dá uma olhada, sabe exatamente o que vai mudar.
- Some ao deletar: depois do rm, não tem credential em texto puro no disco, nem paste de whsec_... no histórico do shell.
Um script por credential: set_stripe_live_key.rb, set_webhook_secret.rb, set_price_ids.rb, set_wallet_address.rb. rm cada um logo depois.
Depois de escrever, não confia que escreveu certo. Lê de volta:
bin/rails runner -e production "
c = Rails.application.credentials
puts 'sk_live set: ' + c.dig(:stripe, :secret_key).to_s.start_with?('sk_live_').to_s
puts 'webhook_secret set: ' + c.dig(:stripe, :webhook_secret).to_s.start_with?('whsec_').to_s
puts 'wallet prefix: ' + c.dig(:x402, :wallet_address).to_s[0..5]
puts 'wallet len: ' + c.dig(:x402, :wallet_address).to_s.length.to_s
"
A chave é a checagem de prefixo e comprimento, não só imprimir os valores.
sk_live_. Se você lê de volta sk_test_, o Claude colocou a test key no arquivo prod — um bug que você não vai achar até o primeiro pagamento real.whsec_. Formato certo ou errado a olho nu.0x.... Comprimento errado significa que algum outro caractere se infiltrou.Adicionar checagens de prefixo bloqueia a maioria dos typos, campos faltando, e ambientes misturados.
Dá vontade de pensar "eu mudo a network pra mainnet antes do deploy". Esse tipo de switch depende da memória humana. Cedo ou tarde vai te queimar.
Coloca a regra no initializer:
# config/initializers/x402.rb
X402.configure do |c|
c.wallet_address = Rails.application.credentials.dig(:x402, :wallet_address)
c.chain = Rails.env.production? ? "base" : "base-sepolia"
end
Mesmo código, testnet (sepolia) em dev, mainnet (base) em prod. Nada pra mudar na hora do deploy. Claude não pode "esquecer de trocar" porque trocar não é trabalho dele.
Mesmo truque pra basescan_tx_url, visibilidade de Plan (planos dev-only não renderizam em prod), seleção de Stripe price ID, etc. Qualquer coisa que difere entre dev e prod, flipa com Rails.env. Não dependa de lembrar na hora do deploy.
Com as quatro guardrails no lugar, você ainda tem que clicar o pagamento de dinheiro real você mesmo da primeira vez.
No artigo anterior Depurando bugs silenciosos com Claude, descrevi o primeiro clique de pagamento x402 em produção: invalid_string at payTo no console. O 43º caractere do endereço da carteira era um ponto de interrogação de largura total que se infiltrou por um IME chinês. Checagens de prefixo não conseguiam pegar (o 0x tava lá), testes não conseguiam pegar (testes não disparam transações reais), só um clique real fez aparecer.
Guardrails de deploy não são pro Claude. São pra você — automatiza o que pode ser automatizado (prefixos, comprimentos, flips de env) pra que a atenção que sobra vá pras interações reais que automação não cobre.
O fluxo completo de Claude-deploy:
.enc + .key distintos.EDITOR=script, deleta imediatamente.Rails.env, não na hora do deploy.Deploy não é "escrever código com stakes maiores". Deploy é "escrever código onde ninguém te diz que você tá errado". As guardrails existem pra transformar "ninguém te diz" em "você sabe em 15 segundos".