Prod Rails tanısı + veri düzeltme: kamal exec, tam genişlikte `?`, Base64, thread sürekliliği.
Üretim 400/500'leri yereldekine hiç benzemez. Yerelde hata alırsan testleri yeniden koşar, düzenler, yeniden koşarsın. Üretimde, kullanıcı çoktan "Öde"ye tıkladı ve boş bir ekrana bakıyor.
Üç yol:
rails console olmayabilir, değişiklik denetlenmez, bir yanlış tuşla yandınBu yazı üçüncü yol üzerine. İki gerçek vaka: credentials'a sıkışan tam genişlikte bir ?'in x402 ödemelerini 500 ile patlattığı durum, ve thread'in ortasındaki bir tweet'in status: :failed olmasıyla thread bütünlüğünü koruyarak yeniden gönderme. Yol boyunca Claude varsayılan olarak 4 farklı yanlış yöne sapmaya çalışacak — her birini yakalaman gerek.
kamal app exec --reuseKamal 37signals'ın Rails dağıtım aracı. Bir komutu var:
kamal app exec --reuse 'bin/rails runner "..."'
--reuse anlamı: yeni container açma, çalışan web container'ının içinde yürüt. Yeni build yok, docker pull yok, yeniden başlatma yok, ENV yeniden enjeksiyonu yok. Komut çalışır, container istekleri karşılamaya geri döner.
Çıktı stdout üzerinden terminaline akar — üretimdeki bir Rails console'da puts yazmış gibi, SSH, tmux veya laptoptan ayrılmak yok.
Tipik oturum:
$ kamal app exec --reuse 'bin/rails runner "puts User.count"'
Launching command with version abc123 from existing container...
INFO [ok] Finished in 3.8 seconds with exit status 0
App Host: deploy.how2claude.com
12847
Gidiş-dönüş 3-6 saniye. Yeniden dağıtımdan iki kat mertebe daha hızlı.
?Commit eba9ac9.
Belirti: x402 ödemelerini yayınladıktan birkaç saat sonra, her ödeme isteği 500 veriyor. Loglar HTTP client'tan Net::HTTPBadResponse ile dolu. Yerel mükemmel çalışıyor.
Tanı: Claude'a önce üretimdeki x402 konfigürasyonunu yazdırmasını söyle:
kamal app exec --reuse 'bin/rails runner "puts X402.configuration.wallet_address.inspect"'
Çıktı:
"0xAbC123...def?"
Kuyrukta fazladan bir ? var — tam genişlikte soru işareti (U+FF1F), yarım genişlikteki ? değil. Birinin IME'si config/credentials/production.yml.enc düzenlenirken değişmiş ve bu karakter sızmış.
Yerel config/credentials.yml.enc (dev/test master.key ile açılan) bu karakteri içermiyor — Rails 8'de production ve dev ayrı encrypted credentials, içerik paylaşılmıyor.
Onarım: SSH yapıp dosyayı direkt düzenleyemezsin (şifreli) ve yerele çekip düzenleyemezsin (master.key laptopta yok). Doğrusu, Claude'a tek kullanımlık bir Ruby script yazdırıp EDITOR= üzerinden credentials:edit'e enjekte etmek:
# script/fix_prod_wallet.rb
content = File.read(ARGV[0])
# Kuyruğun ucundaki tam genişlikte soru işaretini kaldır
content.gsub!(/(wallet_address: 0x[0-9a-fA-F]+)?\s*$/, '\1')
File.write(ARGV[0], content)
EDITOR="ruby script/fix_prod_wallet.rb" \
bin/rails credentials:edit --environment production
credentials:edit akışı: şifre çöz → temp dosyaya yaz → $EDITOR çağır → yeniden şifrele → temp'i sil. EDITOR'ı kendi Ruby scriptimizle değiştirince düzenleme otomatikleşir, şifreli metne yerelde gözle bakmaya gerek kalmaz.
Sonrasında git commit + kamal deploy bir kez — bu deploy zorunlu çünkü production.yml.enc değişti. Ama tanı bir deploy'a mal olmadı.
Kural: Üretim kırıldığında, önce kamal app exec --reuse ile oku. Tahmin etme, önce yeniden dağıtma.
Belirti: Makale yayınlandıktan sonra tweet'ler x_queue_tweets tablosuna gider. 4'lü thread'in 2.'si status: :failed ile biter (X API rate limit'i, içerik doğrulama, ne olursa). Yeniden göndermek 1. tweet ile thread bütünlüğünü korumayı gerektirir.
Başarısızı bul:
kamal app exec --reuse 'bin/rails runner "
XQueue::Tweet.where(status: :failed).each do |t|
puts \"#{t.id}: thread=#{t.thread_id} pos=#{t.thread_position} content=#{t.content.inspect}\"
end
"'
id=87, thread_id=15, thread_position=2 çıkıyor.
Tuzak 1: shell escape. Tweet içeriğinde çoğunlukla tırnak, satır sonu, backtick olur. Şöyle yazarsan:
# patlar — shell tırnakları ve ters eğik çizgileri yer
kamal app exec --reuse 'bin/rails runner "t = XQueue::Tweet.find(87); t.update!(content: \"...\")"'
Base64 dansı — yerelde kodla, base64 dizesini geçir, runner içinde çöz:
# Yerelde kodla
echo -n 'Yeniden yazılmış tweet içeriği...' | base64
# => WWVuaWRlbiB5YXpsbcsIHR3ZWV0IMSxw6Fyacuv...
# Geçir
kamal app exec --reuse "bin/rails runner \"
t = XQueue::Tweet.find(87)
t.update!(content: Base64.decode64('WWVuaWRlbiB5YXpsbcsIHR3ZWV0IMSxw6Fyacuv...'), status: :scheduled)
puts t.status
\""
Base64 dizeleri sadece ASCII, shell güvenli.
Tuzak 2: thread bütünlüğü. XQueue::PostTweetJob.perform_later(87) bağımsız bir tweet yayınlar — tweet #1'e zincirlenmez — çünkü X API reply_to_tweet_id gerektirir ve Job varsayılanda böyle bir değer taşımaz.
Önceki tweet'in x_tweet_id'sini bul (başarılı gönderimler bu alanı doldurur):
kamal app exec --reuse "bin/rails runner \"
t = XQueue::Tweet.find(87)
prev = XQueue::Tweet.where(thread_id: t.thread_id, thread_position: t.thread_position - 1).first
puts 'prev x_tweet_id: ' + prev&.x_tweet_id.to_s
\""
# => prev x_tweet_id: 1834567890123456789
Reply hedefiyle kuyruğa al:
kamal app exec --reuse 'bin/rails runner "
XQueue::PostTweetJob.perform_later(87, reply_to_tweet_id: \"1834567890123456789\")
puts \"enqueued\"
"'
Worker'ın polling_interval'i 0.1 saniye — job'ı neredeyse anında alır. Birkaç saniye sonra kamal app exec ile status'ün scheduled'dan posted'a döndüğünü ve x_tweet_id'nin dolduğunu görürsün — thread kesintisiz.
Kural: Üretim veri operasyonları iş katmanı kısıtlarına saygı duymalı, sadece "kayıt güncellendi" yetmez. Thread bütünlüğü bir iş kısıtıdır; Rails runner bunu senin için kontrol etmez.
Bu tür iş yaparken Claude'un ilk refleksi çoğu zaman yanlış. Her birini yakala:
"Sunucuya SSH yapıp bir bakayım..."
Yönlendir: kamal app exec --reuse SSH'yi yener — container içinde, Rails env yüklü, denetlenir (kamal logları kayıt tutar), host shell'ine dokunmaz, container drift endişesi yok (reuse mevcut üretim sürümünü garanti eder).
"wallet_address'teki tam genişlikte
?'i silmek için bir migration yazayım..."
Yönlendir: Bir credentials değerini değiştirmek migration gerektirmez (DB'ye dokunulmadı). Tek kullanımlık Rails runner bunu 10 saniyede halleder; migration deploy gerektirir ve şemada sonsuza kadar kalır. Sadece düzeltmenin tekrar gerekebileceği durumlarda migration kullan; bir yazım hatası tek seferlik.
"
update!(content: "...")ile olur..."
Yönlendir: Kullanıcı tarafından üretilen her içerik (tweet, yorum, kullanıcı markdown'ı) Base64 üzerinden yönlendirilmeli. Shell'in tırnak, ters eğik çizgi, $ ve backtick parsing'i klasik mayın tarlası — üretim pratik yeri değil.
perform_later"
PostTweetJob.perform_later(87)ile yeniden gönderirim..."
Yönlendir: Önce "bu kayıt diğerleriyle ilişkili mi?" diye sor. Thread'lerin reply_to ilişkisi var, toplu işlerin batch_id ilişkisi, sayfalanmış işlerin cursor ilişkisi — Job'ın argüman listesi o ilişkilerin taşıyıcısıdır. Bir argümanı atla, bir zinciri kır.
Üretim Rails'i Claude ile debug + veri değiştirme — 6 kural:
kamal app exec --reuse 'bin/rails runner "puts X"'. Bir şey değiştirmeden önce problemi lokalize et.kamal app exec --reuse varsayılan araçtır, SSH ya da yeniden dağıtım değil. Container içinde, Rails yüklü, 3-6 saniye gidiş-dönüş.EDITOR=ruby-script bin/rails credentials:edit --environment production ile. Ruby script düzenlemeyi yapar, yerelde şifreli metne bakmaya gerek yok.echo -n 'X' | base64, runner'da Base64.decode64.Üretimde debug, dev akışını üretime taşımak değil — iterasyon alanın yok, hata tolerasın yok. Gerçekten kullandığın şey, üretimin zaten sunduğu iç gözlem yüzeyi (Rails runner + kamal exec + credentials:edit), her adım mümkün olan en küçük değişikliği yapar. Claude doğru Ruby yazabilir — ama "hangisi direkt yapılabilir, hangisi önce tanı gerektirir" kararı senin için veremez. Bu senin üretim disiplindir.