Gli errori di deploy non hanno sintomi. Quattro guardrail — vault separati, script EDITOR, rilettura di verifica, switch runtime per env — per usare Claude senza che bruci tutto.
La grossa differenza tra fare deploy e scrivere codice: il deploy è one-shot, ad alto rischio, doloroso da revertire. Scrivi una riga di codice rotta, un test la prende. Scrivi una riga di credentials rotta, te ne accorgi solo al primo pagamento vero — gli utenti hanno già pagato, i soldi non sono arrivati sul tuo conto, i log sono solo 400.
Di recente ho portato how2claude dal dev locale alla produzione con Claude: account Stripe live, wallet x402 mainnet, Google OAuth, Kamal secrets. Claude non sa quali valori sono di test e quali sono reali. Non sa che sk_live_ vs sk_test_ è una differenza di una lettera con conseguenze apocalittiche. I guardrail li metti tu.
Rails di default usa config/credentials.yml.enc, decifrato da config/master.key. Se usi quel default, hai perso.
Se metti sk_live_xxx in quel file e poi fai girare i test in locale, il tuo codice di test sta usando Stripe produzione. Ogni run di test addebita una carta vera.
Separa in due:
- config/credentials.yml.enc + config/master.key: dev/test, tiene sk_test_xxx
- config/credentials/production.yml.enc + config/credentials/production.key: prod, tiene sk_live_xxx
Entrambi i .key in .gitignore, entrambi i .enc committati. production.key vive solo sulla macchina di deploy.
Poi i Kamal secrets devono puntare al file giusto:
# .kamal/secrets
-RAILS_MASTER_KEY=$(cat config/master.key)
+RAILS_MASTER_KEY=$(cat config/credentials/production.key)
Dentro il container RAILS_MASTER_KEY adesso punta alla chiave di produzione, e quello che viene decifrato sono le credentials di produzione. Ho guardato Claude scrivere questa riga — il template default di Kamal è config/master.key (quello di dev), che fa silenziosamente il deploy della chiave Stripe dev in produzione.
bin/rails credentials:edit --environment production apre un editor interattivo. Claude non può pilotare un editor interattivo. Neanche tu vuoi incollare a mano una dozzina di secret (un typo e si spacca tutto).
Usa questo pattern:
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 fatto così:
# ARGV[0] è il path del file YAML temporaneo decifrato
file = ARGV[0]
require "yaml"
data = YAML.load_file(file) || {}
data["stripe"] ||= {}
data["stripe"]["webhook_secret"] = "whsec_GHWObNAKFh2HPOlJpbGmlYfIiKz1C8EY"
File.write(file, data.to_yaml)
Rails scrive lo YAML decifrato in un file temporaneo, chiama il tuo "EDITOR" passandogli quel path come argomento, e ricifra all'uscita. Il tuo "EDITOR" è solo uno script Ruby che modifica chirurgicamente una chiave e salva.
Perché funziona:
- Preciso: tocca solo stripe.webhook_secret, nient'altro.
- Idempotente: farlo girare due volte è come farlo girare una.
- Auditabile: lo script è il diff. Claude lo scrive, gli dai un'occhiata, sai esattamente cosa cambierà.
- Svanisce al delete: dopo rm non c'è credential in chiaro sul disco, né paste di whsec_... nella shell history.
Uno script per credential: set_stripe_live_key.rb, set_webhook_secret.rb, set_price_ids.rb, set_wallet_address.rb. rm ciascuno subito dopo.
Dopo aver scritto, non fidarti che abbia scritto bene. Rileggi:
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
"
La chiave sono i check di prefisso e lunghezza, non solo stampare i valori.
sk_live_. Se rileggi sk_test_, Claude ha messo la test key nel file prod — un bug che non troverai fino al primo pagamento vero.whsec_. Formato giusto o sbagliato, a colpo d'occhio.0x.... Lunghezza sbagliata vuol dire che un altro carattere si è infilato.Aggiungere i check di prefisso blocca la maggior parte di typo, campi saltati e ambienti mischiati.
È tentante pensare "cambio la network in mainnet prima del deploy". Quel tipo di switch dipende dalla memoria umana. Prima o poi ti brucia.
Incide la regola nell'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
Stesso codice, testnet (sepolia) in dev, mainnet (base) in prod. Niente da cambiare al deploy. Claude non può "dimenticarsi di switchare" perché switchare non è il suo lavoro.
Stessa mossa per basescan_tx_url, visibilità dei Plan (i piani dev-only non si renderizzano in prod), selezione di Stripe price ID, e così via. Qualsiasi cosa differisca tra dev e prod, ribaltala con Rails.env. Non contare sul ricordartene al deploy.
Con tutti e quattro i guardrail a posto, devi comunque cliccare il pagamento di soldi veri tu stesso la prima volta.
Nell'articolo precedente Debuggare i bug silenziosi con Claude, ho descritto il primo click di pagamento x402 in produzione: invalid_string at payTo in console. Il 43° carattere dell'indirizzo wallet era un punto interrogativo fullwidth infilatosi da un IME cinese. I check di prefisso non riuscivano a prenderlo (0x era sempre lì), i test non riuscivano a prenderlo (i test non sparano transazioni vere), solo un click vero l'ha fatto venire fuori.
I guardrail di deploy non sono per Claude. Sono per te — automatizza quello che si può automatizzare (prefissi, lunghezze, switch di env) così l'attenzione che risparmi va alle interazioni vere che l'automazione non copre.
Il flusso completo di Claude-deploy:
.enc + .key distinti.EDITOR=script, cancellalo subito.Rails.env, non al deploy.Il deploy non è "scrivere codice con stakes più alti". Il deploy è "scrivere codice dove nessuno ti dice che hai sbagliato". I guardrail esistono per trasformare "nessuno te lo dice" in "lo sai entro 15 secondi".