部署出错没人告诉你。用独立 credentials、EDITOR 脚本、readback 校验、env 自动切换四道护栏,让 Claude 能参与但不失控。
部署和写代码最大的不同是:一次性、高风险、回滚麻烦。写错一行代码,测试跑一次就发现。写错一行 credentials,你要到第一次真实支付失败才发现——这时候用户已经付了钱、钱没到你账户、日志里全是 400。
最近用 Claude 把 how2claude 从本地开发推上生产,要配 Stripe live 账户、x402 mainnet 钱包、Google OAuth、Kamal secrets。Claude 不知道哪些值是测试环境的、哪些是真的,不知道 sk_live_ 和 sk_test_ 差一个字母就是天壤之别。护栏得自己搭。
Rails 默认的 config/credentials.yml.enc,解密密钥 config/master.key。用这个默认你就输了。
如果你把 sk_live_xxx 塞进这个文件,然后本地跑测试——测试代码就在用生产 Stripe。跑一次就扣一次钱。
换成两个:
- config/credentials.yml.enc + config/master.key:dev/test 用,塞 sk_test_xxx
- config/credentials/production.yml.enc + config/credentials/production.key:生产用,塞 sk_live_xxx
.gitignore 里两个 .key 都忽略,两个 .enc 都 commit。production.key 单独放在机器上。
然后 Kamal secrets 要指对文件:
# .kamal/secrets
-RAILS_MASTER_KEY=$(cat config/master.key)
+RAILS_MASTER_KEY=$(cat config/credentials/production.key)
容器里的 RAILS_MASTER_KEY 指向生产密钥,解出来就是生产 credentials。Claude 写这行的时候我特意盯着——默认生成的 Kamal 模板是 config/master.key(dev 的),完全静默地把 dev Stripe key 部署到生产去。
bin/rails credentials:edit --environment production 要开一个交互式编辑器。Claude 驱动不了交互式编辑器。你也不想手动复制十几个密钥(一个打错全家福崩)。
用这个 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 长这样:
# ARGV[0] 是解密后的临时 YAML 文件路径
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 把解密后的 YAML 写到临时文件,把文件路径当参数调 "EDITOR",退出后重新加密。我们的"EDITOR"实际上是个 Ruby 脚本,精确修改一个键,存回。
好处:
- 精确:只动 stripe.webhook_secret 这一个字段,不动其他。
- 幂等:脚本跑两次结果一样。
- 可审计:脚本本身就是改动的 diff。Claude 写完脚本我看一眼就知道它要改什么。
- 删除即忘:rm 之后磁盘上没有明文 credential 残留,shell history 里也没有 whsec_... 的复制粘贴痕迹。
每个 credential 都写一个脚本:set_stripe_live_key.rb、set_webhook_secret.rb、set_price_ids.rb、set_wallet_address.rb。写完立刻 rm。
写进去之后,不要相信它写对了。读回来:
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
"
关键是前缀和长度校验,不是光打印出来看。
sk_live_ 开头。如果读出来是 sk_test_ 开头,Claude 把 test key 塞进了 prod 文件——这个 bug 在第一次真实支付前你不会发现。whsec_ 开头。格式对不对一眼看出来。0x...。长度不对就说明混进了别的字符。前缀校验加上去,手误、漏字段、混环境这类错误基本都能挡住。
容易想到"在 deploy 前把 network 改成 mainnet"——这种切换靠人记忆,迟早出事。
x402 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
同一份代码,dev 跑 testnet(sepolia),prod 跑 mainnet(base)。部署时不用改任何东西。Claude 也不会"忘了切换",因为切换不需要它。
同样的逻辑用在 basescan_tx_url、Plan 可见性(dev-only plans 生产不显示)、Stripe price ID 选择……凡是 dev 和 prod 不一样的配置,都用 Rails.env 翻,不要依赖部署时记得改。
四道护栏搭完,第一次真金白银的支付仍然必须你自己点一次。
上一篇《让 Claude 调试玄学 bug》讲过,我在生产第一次点 x402 支付,console 直接报 invalid_string at payTo——钱包地址第 43 位是一个从中文输入法混进来的全角问号。前缀校验挡不住(0x 开头没变),测试抓不到(因为测试不发真实交易),只有真实点一次才暴露。
部署的护栏不是给 Claude 看的,是给你看的——把可验证的东西(前缀、长度、env 切换)自动化,省下的人工注意力用来盯那些自动化挡不住的真实交互。
让 Claude 做部署,完整流程:
.key 文件。EDITOR=script 塞进去,立刻 rm。Rails.env 翻转,不要部署时手动切。部署不是"更危险的写代码",部署是"写错了没人马上告诉你"的写代码。护栏的作用是把"没人告诉你"变成"15 秒内就能告诉你"。