5 أخطاء لـ Claude Code في إضافة ووردبريس كبيرة

5 أخطاء لـ Claude Code في إضافة ووردبريس كبيرة

وكيل الذكاء الاصطناعي بيرجعلك بملخص واثق وتستات كلها خضرا، وانت مبسوط إن الباج خلص في دقيقتين. المشكلة إن الأخطاء اللي بتعدّي مش شكلها غلط — شكلها تمام أوي.

فريق قعد يراقب Claude Code وهو بيشتغل على إضافة Easy Digital Downloads، ورصد خمس حالات كل واحدة منها خرجت من تحت ملخص واثق وتست أخضر. وخمسة أسئلة بسيطة كشفتهم كلها في أقل من أربع دقائق.

أرضية التجربة

الكود المستخدم في التجربة هو Easy Digital Downloads، وهي إضافة (ووردبريس لتضيف له ميزة جديدة، مثل متجر أو نموذج تواصل أو تحسين للسيو، دون كتابة كود. اختر الإضافات الموثوقة المحدّثة، ولا…">Plugin) تجارة إلكترونية مفتوحة المصدر بحجم 240,000 سطر من PHP تقريبًا، ومعها مجموعة اختبارات PHPUnit تعمل داخل Docker.

الملاحظة الأولى في المقال الأصلي مهمة: مخرجات الموديل تتغير من جلسة إلى أخرى، فقد تحصل على نتائج أفضل أو أسوأ بنفس التعليمات. الأسئلة الخمسة أدناه تعمل في الحالتين.

ملاحظة: الغرض من المقال ليس إثبات أن وكلاء البرمجة (Coding Agents) غير صالحين للعمل على ووردبريس. في معظم الوقت كان الوكيل يقرأ الملفات الصحيحة ويسمّي السبب الصحيح. الحديث هنا عن بقية الوقت.

1. إصلاح السطر الظاهر في رسالة الخطأ لا مصدر العلة

الشكوى كانت أن صفحة المنتجات في لوحة التحكم (Dashboard) تنهار عند ضبط لغة الإدارة على الروسية، مع رسالة ValueError: Missing format specifier at end of string داخل src/Admin/Promos/Notices/License_Upgrade_Notice.php عند السطر 159.

التشخيص كان صحيحًا في معظمه. استنتج الوكيل أن النص الروسي يحتوي على علامة % مفردة في موضع تحتاج فيه الدالة إلى %%، وكتب سكربتين صغيرين لطباعة النصوص من حزمة الترجمة (Language Pack) فعثر على النص المعطوب.

ثم عدّل السطر 159 نفسه، فاستبدل printf بـ str_replace، ونجحت الاختبارات في دقيقتين وست ثوانٍ.

المشكلة أن التعديل صحيح بالنسبة للسطر الذي انهار فقط. القيمة المعطوبة تأتي من مكان آخر، والبحث في حزمة الترجمة عن النمط %d% يُرجع أربعة أزواج من النصوص، وكل نص روسي فيها يحمل العلامة المفردة نفسها. هذه النصوص تُعرض في أربعة مواضع أخرى.

اختبار ثانٍ على رسالة عمولة Stripe، مع الكود المعدَّل، أعطى النتيجة التالية:

ArgumentCountError: 3 arguments are required, 2 given  
.../src/Gateways/Stripe/ApplicationFee.php:176  

نفس السبب، ورسالة خطأ مختلفة. الانهيار انتقل مكانه ولم يُصلَح.

تفصيلة أخيرة تستحق الانتباه: ملف CLAUDE.md الخاص بالمشروع كان يمنع الوكيل من قراءة مجلد languages/. قاعدة منطقية توفّر وقت البحث في كود ضخم، لكنها في هذه العلة تحديدًا أوقفته على بُعد مجلد واحد من السبب الحقيقي.

تنبيه: الخطأ هذا ليس حالة روسية خاصة. الترجمة العربية معرّضة له بنفس الدرجة، لأن أي مترجم على translate.wordpress.org يكتب % بدل %% في نص يمر على printf يُسقط الصفحة كلها. إذا كنت تدير موقعًا بالعربية وواجهت ValueError بعد تحديث حزمة ترجمة، ابدأ من ملف .po لا من ملف PHP.

القاعدة: اقرأ فرق التعديلات (Diff) عند موضع الانهيار في النهاية. اسأل أولًا: من أين جاءت القيمة المعطوبة؟ وإذا كان الجواب ملفًا لم يلمسه التعديل، فاعتبر التعديل معالجة لعَرَض حتى يثبت العكس.

2. مطلب تحوّل إلى تعليق داخل الكود

الطلب كان من ثلاثة أجزاء: إضافة اختبار في tests/gateways/tests-gateways.php يسجّل بوابة دفع (Payment Gateway) عبر الخطّاف (Hook) edd_registered_gateways، والتحقق من أن EDDGatewaysRegistry::get() تُرجعها، مع نجاح الكلاس بالكامل.

في src/Gateways/Registry.php فخ لم يضعه أحد لأجل التجربة. الدالة get() تحتفظ بالبوابات في متغير static داخلها، فأول نداء يملؤه ولا شيء من الخارج يُفرغه. وبما أن اثني عشر اختبارًا سابقًا في الكلاس نفسه تمر على get()، فالذاكرة تكون ممتلئة قبل وصول الاختبار الجديد، وأي فلتر متأخر لا يغيّر شيئًا.

الوكيل رأى المتغير الساكن فورًا، وأعاد كتابة الاختبار ست مرات قبل تشغيله، ورفض — بشكل صحيح — تعديل كود الإنتاج من أجل اختبار. بعد فشل التشغيل الأول نقل التحقق إلى الدالة الخاصة get_registered_classes() عبر الـ Reflection، وأضاف فلترًا ثانيًا على edd_payment_gateways، وبنى تأكيده على النتيجة الأخيرة.

الملخص قال إن الاختبار يتحقق من ظهور البوابة عبر الـ API العام من البداية إلى النهاية. النتيجة: ثماني دقائق، 62 سطرًا، والكلاس كله أخضر.

كشف الحالة كلّف سطرًا واحدًا. الأمر التالي يبدو مطمئنًا:

git diff | grep -c "Registry::get()"  

النتيجة 2، أي أن الدالة المطلوبة موجودة. لكن تشغيل grep -n بدل grep -c يكشف أن كلا الموضعين تعليق، أحدهما يشرح أن get() تحتفظ بالنتيجة في متغير ساكن ولذلك يستخدم الاختبار طريقًا آخر. الاختبار لا ينادي الدالة التي كُتب من أجلها، ومع الفلتر الثاني في مكانه لا يمكن لتأكيده أن يفشل أصلًا.

القاعدة: قارن المطلوب بالمنفَّذ قبل أن تقارن الكود. كلمة “نجح” تجيب على السؤال الذي اختاره الوكيل، وقائمتك المكتوبة تجيب على سؤالك. وإذا كان الاختبار لا يمكن كتابته كما طُلب بصدق، فالمخرَج الصحيح جملة تقول ذلك.

3. إعادة هيكلة بقيت خضراء وغيّرت السلوك

ثلاث دوال مساعدة لبطاقات الدفع في includes/checkout/functions.php، إحداها edd_purchase_form_validate_cc_exp_date(). الطلب: تنظيفها وتحديثها دون تغيير السلوك، ثم تشغيل اختبارات صفحة الدفع (Checkout).

أعادت الجلسة كتابة الثلاث في مرة واحدة، وبُني التحقق من تاريخ الانتهاء حول DateTime::createFromFormat( '!Y-n', ... ). ثم شغّلت 241 اختبارًا بثلاث حالات فاشلة وصفتها بأنها أعطال معروفة سابقًا، وانتهى التقرير بعبارة أن السلوك مطابق للأصل.

مجموعة الاختبارات وافقت على ذلك. لكن الأمر grep -rn edd_purchase_form_validate_cc_exp_date tests/ لا يطبع شيئًا، ولا شيء آخر في المستودع ينادي الدالة. هي واجهة عامة تستخدمها إضافات بوابات الدفع، ومن المستحيل أن تفشل الاختبارات بسببها مهما فعلت إعادة الهيكلة.

ثلاث حالات فحص يدوية حسمت الأمر: الشهر '12' مع السنة '27' يجب أن يكون صالحًا، و'12' مع '2027' صالحًا، و'12' مع '2020' منتهيًا.

على الكود الجديد تفشل الحالة الأولى: بطاقة صالحة حتى ديسمبر 2027 تُرفض كمنتهية. وعلى الكود الأصلي تنجح الثلاث، لأنه كان يمر على strtotime التي تقرأ Dec 27 كديسمبر 2027. الحرف Y الكبير في صيغة التاريخ في PHP يعني أربعة أرقام، فتفشل قراءة '27' ويُرجع التحقق الجديد false.

والأسوأ أن نموذج الدفع نفسه يعرض السنوات برقمين، أي أن الإضافة التي تنقل ما يراه العميل حرفيًا هي بالضبط الطرف الذي وُجدت هذه الدالة لخدمته.

ملاحظة: هذه النقطة تخصّ المتاجر المصرية والخليجية أكثر من غيرها، لأن ربط بوابات محلية مثل Paymob أو Fawry أو Kashier يمر عادة عبر إضافة وسيطة تنادي دوال التحقق هذه بدل أن تكتب تحققها الخاص. انحدار برمجي (Regression) هنا يظهر كرفض بطاقات سليمة، وهو آخر شيء تكتشفه من لوحة التحكم.

القاعدة: التشغيل الأخضر يثبت الاختبارات الموجودة لا التعديل نفسه. قبل أن تقبل عبارة “لا تغيير في السلوك”، ابحث عن تغطية اختبارية لكل دالة لمسها التعديل. وحيث لا توجد تغطية، ثبّت السلوك الحالي باختبار جديد أولًا، أو أعلن صراحة أن التعديل غير متحقَّق منه.

4. ثلاثة أسطر رجعت 28 سطرًا

طلب مفتوح في المستودع رقم #9819 يريد إمكانية تحديد عدد الخانات العشرية في edd_format_discount_rate(). وبما أن edd_format_amount() تقبل هذا المعامل بالفعل، فأصغر تعديل ممكن هو تمريره:

function edd_format_discount_rate( $type = '', $amount = '', $decimals = true ) {  
    return ( 'flat' === $type )  
        ? edd_currency_filter( edd_format_amount( $amount, $decimals ) )  
        : edd_format_amount( $amount, $decimals ) . '%';  
}  

ثلاثة أسطر معدّلة، وكل من ينادي الدالة حاليًا يحصل على نفس المخرَج.

صيغ الطلب بلغة صاحب متجر: نِسَب الخصم تظهر بالشكل “10.00%”، وأريد أن يستطيع المالك اختيار عدد الخانات. جاء التعديل 28 سطرًا مضافًا وثلاثة محذوفة، ومعه فلتر جديد باسم edd_discount_rate_decimals يُرجع null ما لم يربطه أحد.

وعند null تمر القيمة على floatval()، فتصبح “10.00%” هي “10%” في كل متجر. وإذا أعاد الفلتر رقمًا فإنه يذهب مباشرة إلى number_format() في PHP متجاوزًا دالة التنسيق الأصلية edd_format_amount(). وكتب الوكيل ملف اختبار جديدًا بسبع حالات، كلها ناجحة.

من الإنصاف القول إن الملخص بدأ بعبارة أن السلوك الافتراضي تغيّر. لكنه يبقى قرارًا لم يطلبه أحد، اتُّخذ نيابة عن كل متجر يشغّل الإضافة.

الملف الأخضر كان يختبر سلوكًا اخترعته الجلسة قبل دقائق. والكشف هنا حسابي بحت:

git diff --stat      # 28 سطرًا مقابل 3 كنت تحتاجها  
git status --short   # يكشف ملف الاختبار الجديد غير المتتبَّع  

ملف الاختبار الجديد لا يظهر في git diff --stat أصلًا، لأن هذا الأمر لا يعرض الملفات غير المتتبَّعة. وتشغيل مجموعة الخصومات التي تخطّتها الجلسة يُعطي 255 اختبارًا بثلاث حالات فاشلة، كل واحدة تتوقع '20.00%' وتستقبل '20%'. ولا واحد من هذه الاختبارات كان مخطئًا؛ كلها تصف ما كانت المتاجر تعمل به بالأمس.

القاعدة: اعدد الأسطر التي طلبتها مقابل الأسطر التي حصلت عليها قبل أن تقرأ سطرًا واحدًا من التعديل. وإذا كانت الفجوة كبيرة، فاقرأ الأسطر الزائدة كقرارات تصميم اتخذها شخص آخر.

5. جلسة قرأت ثلاث دقائق ولم تعدّل شيئًا

الحالة الشائعة المتوقعة هي أن يدخل الوكيل في حلقة أخطاء لا تنتهي. حاول الفريق إنتاج هذه الحلقة بسبع طرق مختلفة فلم تحدث. ما حدث كان أهدأ وبنفس التكلفة.

التعليمة كانت سطرًا واحدًا فقط: أصلح العلة التي تجعل البحث عن عميل بالبريد الإلكتروني يُرجع نتيجة فارغة.

بعد 53 ثانية كان الوكيل قد وصل إلى الأسطر الصحيحة في includes/admin/customers/class-customer-table.php، حيث يضبط الكود القيمة id__in على array( null ) عندما لا يجد صفًا في جدول عناوين البريد، فيضمن نتيجة فارغة رغم وجود العميل.

وبدل أن يعدّل، انتقل إلى قراءة كلاس ومنشئ الاستعلامات مع القالب كاملًا والنماذج والنوافذ المنبثقة.">Elementor.">JetEngine تبني الاستعلامات بصريًا بمنشئ…">الاستعلام الأساسي، ثم باني شرط WHERE، ثم مسار البحث عبر AJAX، ثم الاختبارات. ظهر src/Database/Query.php في ثلاثة نداءات منفصلة. وعند الدقيقة الثالثة و18 ثانية: إحدى عشرة جولة بحث وقراءة، وصفر تعديل.

في تجربة موازية تُركت نفس التعليمة تعمل حتى النهاية: 29 دقيقة، 92 نداء أداة، أول تعديل عند النداء الثمانين، في src/Database/Queries/Customer.php — أي الكلاس الذي يمر عليه كل استعلام عميل في الإضافة — ثم 4,443 اختبارًا بلا انحدار جديد. التكلفة: 5.09 دولار (حوالي 250 جنيه مصري بسعر تقريبي).

ما نجح فعلًا هو الضغط على Escape عند الدقيقة الثالثة مع الإبقاء على الجلسة، لأن التشخيص الصحيح كان موجودًا في سياقها ومدفوعًا ثمنه بالفعل. ثم صياغة الطلب من جديد بثلاثة عناصر:

  1. العَرَض مع شرطه: البحث يفشل عندما لا يوجد صف في جدول عناوين البريد.
  2. العنوان: الدالة edd_get_customer_by() في includes/customer-functions.php تتراجع بالفعل إلى جدول العملاء، فافعل الشيء نفسه.
  3. السياج: لا تلمس البحث الجزئي بالبريد، ولا معالجة علامة الزائد، ولا أي فرع آخر من parse_args.

بعد دقيقة و39 ثانية: تعديل واحد، سبعة أسطر مضافة وسطر محذوف، في الملف الذي يملك العلة، و202 اختبار عميل ناجح. التكلفة: 0.27 دولار (أقل من 15 جنيهًا).

القاعدة: حدّد ميزانية قراءة قبل أن تبدأ، لأنك بعد أن تدخل في قراءة التحليل ستجد دائمًا سببًا لمنحه دقيقة أخرى. ثلاث علامات، أي واحدة منها تكفي لإيقاف الجلسة: ثلاث دقائق بلا تعديل، أو قراءة نفس الملف مرتين دون تغيير بينهما، أو الإعلان عن منهج جديد بلا تعديل خلف المنهج السابق.

المراجعة في خمس دقائق

الأسئلة الخمسة مرتبة من الأرخص إلى الأغلى، وتصلح لأي تعديل ينتجه وكيل برمجة:

  1. من أين جاءت القيمة المعطوبة؟ اقرأ التعديل عند موضع الانهيار في النهاية.
  2. ما الذي طلبته أنت؟ اكتب البنود، ثم علّم على كل بند، ثم انظر إلى ما تغيّر زيادة.
  3. هل توجد تغطية اختبارية للدوال التي لمسها التعديل؟ الأخضر يثبت الاختبارات لا التعديل.
  4. كم سطرًا طلبت وكم سطرًا استلمت؟
  5. هل تكرر شيء في المحاولات الثلاث الأخيرة؟ نفس الملف أو نفس الخطأ مرتين يعني توقّف واحتفظ بالتشخيص.

على التعديلات الخمسة كلها، ومع ساعة على الشاشة، استغرقت المراجعة ثلاث دقائق و51 ثانية.

في ووردبريس: كل قاعدة من هذه القواعد تُكتب في ملف CLAUDE.md داخل المشروع، تمامًا كما تكتب قواعد الكود في phpcs.xml. لكن التجربة نفسها تحذّر من الاعتماد الكامل على ذلك: بعد إضافة القواعد وإعادة تشغيل الحالة الأولى في جلسة جديدة، دخل الوكيل فعلًا إلى حزمة الترجمة وأصلح النص وحصّن كود PHP ضد المترجمين. ومع ذلك بقي نص عمولة Stripe بنفس العلامة المفردة على مسافة سطرين من تعديله، وظل اختباره فاشلًا.

القاعدة المكتوبة تقدّم الوكيل خطوة. المراجعة هي التي تلتقط الباقي.

الأسئلة الشائعة

س: هل معنى ذلك أن استخدام وكلاء البرمجة في ووردبريس فكرة سيئة؟

ج: لا. في معظم الجلسات قرأ الوكيل الملفات الصحيحة وسمّى السبب الصحيح وشغّل الاختبارات دون طلب متكرر. المطلوب هو مراجعة من خمس دقائق قبل الدمج، لا الامتناع عن الأداة.

س: ما الفرق بين “الاختبارات خضراء” و”التعديل صحيح”؟

ج: الاختبار الأخضر يعني أن الاختبارات الموجودة لم تفشل. إذا كانت الدالة المعدَّلة بلا تغطية اختبارية من الأصل، فالنتيجة الخضراء لا تحمل أي معلومة عن التعديل.

س: كيف أمنع الوكيل من إعادة كتابة أجزاء لم أطلبها؟

ج: ضع سياجًا صريحًا في الطلب يسمّي ما يجب ألا يُلمس، واعدد الأسطر بعد الانتهاء عبر git diff --stat مع git status --short لكشف الملفات الجديدة غير المتتبَّعة.

س: هل ملف CLAUDE.md يحل المشكلة؟

ج: يقلّلها ولا يُلغيها، وقد يسبّبها أيضًا. في الحالة الأولى كانت قاعدة “لا تقرأ مجلد languages” هي ما أبعد الوكيل عن السبب الحقيقي، فراجع قواعدك المانعة بنفس جدّية مراجعة الكود.

س: كم تكلّف الجلسة الواحدة تقريبًا؟

ج: في التجربة، الجلسة المفتوحة بلا توجيه كلّفت 5.09 دولار (حوالي 250 جنيهًا) في 29 دقيقة، والجلسة نفسها بعد إعادة صياغة الطلب كلّفت 0.27 دولار في أقل من دقيقتين. صياغة الطلب هي أرخص أداة تحكم في التكلفة.

الخلاصة

الأخطاء الخمسة لم تظهر كأخطاء: كلها انتهت بملخص واثق، ومعظمها بتشغيل أخضر. وكل واحدة منها كُشفت بسؤال واحد في الطرفية، ومجموعها أربع دقائق تقريبًا.

اعتبر مخرَج الوكيل مثل Pull Request من مطوّر جديد سريع وذكي لا يعرف تاريخ مشروعك: تقرأه قبل الدمج، لا بعده.

جرّب الأسئلة الخمسة على آخر تعديل عمله لك وكيل برمجة، وقولي في التعليقات كام سؤال منهم صاد حاجة فعلًا.

مرجع المقال (بالإنجليزية): Five ways Claude Code failed on a 240,000-line WordPress plugin
المقال ده مش ترجمة حرفية: متكيّف للقارئ العربي ومضاف عليه سياق ووردبريس وElementor وJetEngine.

اترك تعليقاً