تشخيص Rails الإنتاج + إصلاح البيانات: kamal exec، `?` عرض كامل، Base64، استمرارية thread.
أخطاء 400/500 في الإنتاج مختلفة تماماً عن المحلي. محلياً تُعيد تشغيل الاختبارات، تُعدّل، وتُعيد التشغيل. في الإنتاج، المستخدم ضغط "ادفع" بالفعل ويُحدّق في شاشة فارغة.
ثلاثة مسارات:
rails console داخل الحاوية، التغييرات بلا تدقيق، خطأ كتابي واحد ويقع الكارثةيغطي هذا المقال المسار الثالث. حالتان حقيقيتان: ? بالعرض الكامل انحشر في credentials وعطّل مدفوعات x402 بخطأ 500، وتغريدة في منتصف thread حالتها status: :failed تحتاج إعادة إرسال مع الحفاظ على اتصال thread. خلال الطريق، سيحاول Claude الانزلاق في 4 اتجاهات خاطئة افتراضياً — عليك التصدي لكل واحد.
kamal app exec --reuseKamal هي أداة نشر Rails من 37signals. لديها أمر:
kamal app exec --reuse 'bin/rails runner "..."'
معنى --reuse: لا تُنشئ حاوية جديدة، نفّذ داخل حاوية web العاملة. بلا build جديد، بلا docker pull، بلا إعادة تشغيل، بلا إعادة حقن ENV. الأمر يعمل، الحاوية تعود لمعالجة الطلبات.
المخرجات تتدفق عبر stdout إلى طرفيتك — يساوي puts داخل Rails console الإنتاجي، بلا SSH، بلا tmux، بلا مغادرة حاسوبك.
جلسة نموذجية:
$ 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
ذهاب وإياب 3-6 ثوان. أسرع برتبتين من إعادة النشر.
? بالعرض الكامل سبّب 500 في الإنتاجcommit eba9ac9.
الأعراض: بعد ساعات من إطلاق مدفوعات x402، كل طلب دفع يعود بـ 500. السجلات مليئة بـ Net::HTTPBadResponse من HTTP client. المحلي يعمل بشكل مثالي.
التشخيص: اطلب من Claude طباعة إعدادات x402 في الإنتاج أولاً:
kamal app exec --reuse 'bin/rails runner "puts X402.configuration.wallet_address.inspect"'
المخرجات:
"0xAbC123...def?"
هناك ? زائدة في الذيل — علامة استفهام بالعرض الكامل (U+FF1F)، وليست ? بنصف العرض. IME لشخص ما تبدّل أثناء تعديل config/credentials/production.yml.enc، وانزلق هذا الحرف.
config/credentials.yml.enc المحلي (المفكوك بمفتاح master.key للتطوير/الاختبار) لا يحتوي هذا — في Rails 8، production و dev هما encrypted credentials منفصلان، المحتويات لا تُشارك.
الإصلاح: لا يمكنك SSH وتعديل الملف مباشرة (مشفر)، ولا سحبه محلياً وتعديله (master.key ليس على حاسوبك). الحل هو أن يكتب Claude script Ruby لمرة واحدة ويحقنه عبر EDITOR= إلى credentials:edit:
# script/fix_prod_wallet.rb
content = File.read(ARGV[0])
# احذف علامة الاستفهام بالعرض الكامل من الذيل
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: فك التشفير → الكتابة إلى ملف مؤقت → استدعاء $EDITOR → إعادة التشفير → حذف المؤقت. استبدال EDITOR بسكريبت Ruby خاصتنا يُؤتمت التعديل، دون الحاجة للنظر إلى النص المشفر محلياً.
بعد ذلك، git commit + kamal deploy مرة واحدة — هذا النشر إلزامي لأن production.yml.enc تغيّر. لكن التشخيص لم يُكلّف نشراً.
القاعدة: عندما يتعطل الإنتاج، اقرأ أولاً باستخدام kamal app exec --reuse. لا تُخمّن، ولا تُعد النشر أولاً.
الأعراض: بعد نشر مقال، تدخل التغريدات إلى جدول x_queue_tweets. إحداها — الثانية في thread من 4 — تنتهي بـ status: :failed (حد معدل X API، التحقق من المحتوى، أي سبب). إعادة إرسالها يتطلب الحفاظ على اتصال thread مع التغريدة الأولى.
إيجاد الفاشلة:
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.
المطب 1: إفلات shell. محتوى التغريدة غالباً يحتوي علامات اقتباس، أسطر جديدة، backticks. لو كتبت:
# ينفجر — shell يأكل علامات الاقتباس والشرطات المائلة العكسية
kamal app exec --reuse 'bin/rails runner "t = XQueue::Tweet.find(87); t.update!(content: \"...\")"'
رقصة Base64 — شفّر محلياً، مرّر سلسلة base64، فك التشفير داخل runner:
# شفّر محلياً
echo -n 'محتوى التغريدة المُعاد كتابته...' | base64
# => 2YXYrdiq2YjZiiDYp9mE2KrYutix2YrYr9ipIA==
# مرّره
kamal app exec --reuse "bin/rails runner \"
t = XQueue::Tweet.find(87)
t.update!(content: Base64.decode64('2YXYrdiq2YjZiiDYp9mE2KrYutix2YrYr9ipIA=='), status: :scheduled)
puts t.status
\""
سلاسل Base64 ASCII فقط، آمنة للـ shell.
المطب 2: اتصال thread. XQueue::PostTweetJob.perform_later(87) ينشر تغريدة مستقلة — لا يتسلسل مع التغريدة الأولى — لأن X API يحتاج reply_to_tweet_id، و Job افتراضياً لا يحمل هذه القيمة.
إيجاد x_tweet_id للتغريدة السابقة (الإرسالات الناجحة تملأ هذا الحقل):
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
ضعها في الطابور مع هدف الرد:
kamal app exec --reuse 'bin/rails runner "
XQueue::PostTweetJob.perform_later(87, reply_to_tweet_id: \"1834567890123456789\")
puts \"enqueued\"
"'
polling_interval للـ worker هو 0.1 ثانية — يلتقط job فوراً تقريباً. بعد ثوان، kamal app exec لرؤية status ينتقل من scheduled إلى posted و x_tweet_id يُملأ — thread متصل.
القاعدة: عمليات بيانات الإنتاج يجب أن تحترم قيود طبقة الأعمال، لا مجرد "السجل تحدّث بنجاح". اتصال thread قيد أعمال؛ Rails runner لن يفحصه بالنيابة عنك.
عند القيام بهذا النوع من العمل، الحدس الأول لـ Claude غالباً خاطئ. امسك كل واحد:
"دعني أُجري SSH وألقي نظرة..."
أعد التوجيه: kamal app exec --reuse يتفوق على SSH — داخل الحاوية، env Rails محمّل، مُدقّق (سجلات kamal تحتفظ بالسجل)، لا يمس shell المضيف، بلا قلق من container drift (reuse يضمن الإصدار الإنتاجي الحالي).
"سأكتب migration لإزالة
?بالعرض الكامل من wallet_address..."
أعد التوجيه: تغيير قيمة credentials واحدة لا يحتاج migration (قاعدة البيانات لم تُمس). Rails runner لمرة واحدة يُنجزها في 10 ثوان؛ migration يحتاج نشراً ويعيش في schema إلى الأبد. استخدم migration فقط إذا كان الإصلاح قد يحتاج إلى إعادة التشغيل؛ خطأ كتابي هو شيء لمرة واحدة.
"سأستدعي
update!(content: "...")مباشرة..."
أعد التوجيه: أي محتوى يُنشئه المستخدم (تغريدات، تعليقات، markdown مُدخل) يجب أن يمر عبر Base64. تحليل shell لعلامات الاقتباس والشرطات المائلة العكسية و $ و backticks حقل ألغام كلاسيكي — الإنتاج مكان سيء للتمرن.
perform_later بدون معاملات أعمال"سأُعيد بـ
PostTweetJob.perform_later(87)فقط..."
أعد التوجيه: اسأل أولاً "هل هذا السجل مرتبط بآخرين؟" لـ thread علاقة reply_to، ولمهام الدفعات علاقة batch_id، ولمهام الترقيم علاقة cursor — قائمة معاملات Job هي حاملة تلك العلاقات. إسقاط معامل واحد يكسر سلسلة.
تصحيح + تعديل بيانات Rails الإنتاجية مع Claude — 6 قواعد:
kamal app exec --reuse 'bin/rails runner "puts X"'. حدد المشكلة قبل تغيير أي شيء.kamal app exec --reuse هو الأداة الافتراضية، ليست SSH، ليست إعادة نشر. داخل الحاوية، Rails محمّل، ذهاب وإياب 3-6 ثوان.EDITOR=ruby-script bin/rails credentials:edit --environment production. سكريبت Ruby يُنفذ التعديل، دون النظر إلى النص المشفر محلياً.echo -n 'X' | base64 محلياً، Base64.decode64 في runner.التصحيح في الإنتاج ليس نقل تدفق التطوير إلى الإنتاج — ليس لديك مساحة تكرار، ولا تسامح مع الخطأ. ما تستخدمه فعلاً هو سطح التأمل الذاتي الذي يوفره الإنتاج أصلاً (Rails runner + kamal exec + credentials:edit)، كل خطوة تُجري أصغر تغيير ممكن. Claude يستطيع كتابة Ruby صحيح — لكن معرفة "ما الذي يمكن فعله مباشرة مقابل ما الذي يجب تشخيصه أولاً" قرار لن يتخذه بالنيابة عنك. هذا انضباطك الإنتاجي.