Free

تصحيح Rails الإنتاج مع Claude، دون SSH أو إعادة نشر

تشخيص Rails الإنتاج + إصلاح البيانات: kamal exec، `?` عرض كامل، Base64، استمرارية thread.


أخطاء 400/500 في الإنتاج مختلفة تماماً عن المحلي. محلياً تُعيد تشغيل الاختبارات، تُعدّل، وتُعيد التشغيل. في الإنتاج، المستخدم ضغط "ادفع" بالفعل ويُحدّق في شاشة فارغة.

ثلاثة مسارات:

  1. SSH إلى الخادم — يعمل، لكن قد لا يوجد rails console داخل الحاوية، التغييرات بلا تدقيق، خطأ كتابي واحد ويقع الكارثة
  2. إعادة النشر — ارفع إصلاحاً، على الأقل 10+ دقائق، احتمال انقطاع قصير، وكثير من المشاكل ليست مشاكل كود (بل بيانات أو credentials)
  3. تشغيل Rails runner داخل الحاوية العاملة — بلا إعادة تشغيل، بلا إعادة نشر، بلا SSH، دقة مبضع

يغطي هذا المقال المسار الثالث. حالتان حقيقيتان: بالعرض الكامل انحشر في credentials وعطّل مدفوعات x402 بخطأ 500، وتغريدة في منتصف thread حالتها status: :failed تحتاج إعادة إرسال مع الحفاظ على اتصال thread. خلال الطريق، سيحاول Claude الانزلاق في 4 اتجاهات خاطئة افتراضياً — عليك التصدي لكل واحد.


الأداة: kamal app exec --reuse

Kamal هي أداة نشر 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 ثوان. أسرع برتبتين من إعادة النشر.

الحالة 1: بالعرض الكامل سبّب 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. لا تُخمّن، ولا تُعد النشر أولاً.

الحالة 2: إعادة إرسال XQueue::Tweet فاشلة في منتصف thread

الأعراض: بعد نشر مقال، تدخل التغريدات إلى جدول 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 لن يفحصه بالنيابة عنك.

الاتجاهات الـ 4 التي ينحرف إليها Claude افتراضياً (وكيف تُعيد التوجيه)

عند القيام بهذا النوع من العمل، الحدس الأول لـ Claude غالباً خاطئ. امسك كل واحد:

1. يريد SSH إلى الخادم

"دعني أُجري SSH وألقي نظرة..."

أعد التوجيه: kamal app exec --reuse يتفوق على SSH — داخل الحاوية، env Rails محمّل، مُدقّق (سجلات kamal تحتفظ بالسجل)، لا يمس shell المضيف، بلا قلق من container drift (reuse يضمن الإصدار الإنتاجي الحالي).

2. يريد كتابة migration لإصلاح البيانات

"سأكتب migration لإزالة بالعرض الكامل من wallet_address..."

أعد التوجيه: تغيير قيمة credentials واحدة لا يحتاج migration (قاعدة البيانات لم تُمس). Rails runner لمرة واحدة يُنجزها في 10 ثوان؛ migration يحتاج نشراً ويعيش في schema إلى الأبد. استخدم migration فقط إذا كان الإصلاح قد يحتاج إلى إعادة التشغيل؛ خطأ كتابي هو شيء لمرة واحدة.

3. يحشر أحرفاً خاصة في سلاسل shell

"سأستدعي update!(content: "...") مباشرة..."

أعد التوجيه: أي محتوى يُنشئه المستخدم (تغريدات، تعليقات، markdown مُدخل) يجب أن يمر عبر Base64. تحليل shell لعلامات الاقتباس والشرطات المائلة العكسية و $ و backticks حقل ألغام كلاسيكي — الإنتاج مكان سيء للتمرن.

4. perform_later بدون معاملات أعمال

"سأُعيد بـ PostTweetJob.perform_later(87) فقط..."

أعد التوجيه: اسأل أولاً "هل هذا السجل مرتبط بآخرين؟" لـ thread علاقة reply_to، ولمهام الدفعات علاقة batch_id، ولمهام الترقيم علاقة cursor — قائمة معاملات Job هي حاملة تلك العلاقات. إسقاط معامل واحد يكسر سلسلة.

قائمة التحقق

تصحيح + تعديل بيانات Rails الإنتاجية مع Claude — 6 قواعد:

  1. اقرأ قبل أن تكتب. kamal app exec --reuse 'bin/rails runner "puts X"'. حدد المشكلة قبل تغيير أي شيء.
  2. kamal app exec --reuse هو الأداة الافتراضية، ليست SSH، ليست إعادة نشر. داخل الحاوية، Rails محمّل، ذهاب وإياب 3-6 ثوان.
  3. تغييرات credentials عبر EDITOR=ruby-script bin/rails credentials:edit --environment production. سكريبت Ruby يُنفذ التعديل، دون النظر إلى النص المشفر محلياً.
  4. إفلات shell حقل ألغام؛ وجّه المحتوى الخاص عبر Base64. echo -n 'X' | base64 محلياً، Base64.decode64 في runner.
  5. Rails runner لن يُطبّق قيود الأعمال نيابة عنك. reply_to لـ thread، batch_id، cursor الترقيم — ضعها جميعاً في الطابور.
  6. اكتب migration فقط إذا كان "هذا قد يحدث مرة أخرى". إصلاحات البيانات لمرة واحدة تذهب عبر runner، أسرع برتبتين.

التصحيح في الإنتاج ليس نقل تدفق التطوير إلى الإنتاج — ليس لديك مساحة تكرار، ولا تسامح مع الخطأ. ما تستخدمه فعلاً هو سطح التأمل الذاتي الذي يوفره الإنتاج أصلاً (Rails runner + kamal exec + credentials:edit)، كل خطوة تُجري أصغر تغيير ممكن. Claude يستطيع كتابة Ruby صحيح — لكن معرفة "ما الذي يمكن فعله مباشرة مقابل ما الذي يجب تشخيصه أولاً" قرار لن يتخذه بالنيابة عنك. هذا انضباطك الإنتاجي.