İçeriğe Atla
Mustafa Erbay
Teknoloji İnsan tarafından yazıldı Production Diaries · 8 dk okuma · görüntülenme Read in English
100%

AI İçerik Pipeline'ımda Bir Akşamlık Quirk Avı: 3 Bug 1 Sanitizer

AI içerik pipeline'ım üç farklı format quirk'iyle patladı: slash'lı tag, tırnaklı tarih, dotted-i karakter. Tek normalizer ile çözüm.

AI İçerik Pipeline'ımda Bir Akşamlık Quirk Avı: 3 Bug 1 Sanitizer — yaşanmış hikaye kapak görseli

Üç saat içinde üç farklı AI bug’ı

Bugün akşam saat 18:00’a kadar her şey güzel akıyordu. Saatlik content-generate cron’um 24 yazı/gün ritmini tutturmuştu. Sonra bir anda dört arka arkaya cron başarısız oldu. Pipeline-health monitor henüz mail atmamıştı çünkü 4 saatlik eşik dolmamıştı, ama GitHub Actions paneli kırmızıya gidiyordu.

Açıp baktım — üç farklı sebepten patlamış. Hepsi de Gemini’nin ürettiği içerikteki küçük format tuhaflıkları. Hiçbiri “AI yanlış cevap verdi” değil, hepsi küçük YAML/karakter aykırılıkları ki Astro Zod validator’ı acımasızca reddediyor.

Bu yazı, üç bug’ı nasıl avladığım ve sonunda hepsini tek bir auto-fixer ile çözdüğümün hikayesi.

Bug #1: Tag içinde slash, /tags route’u kırıyor

İlk fail mesajı:

[ImageNotFound] route /tags/CI%2FCD g%C3%BCvenli%C4%9Fi failed
TypeError: Cannot read properties of undefined (reading 'split')

Astro’nun /tags/[tag]/index.astro route’u var. Tag string’i URL parametresi olarak geliyor. AI tag listesinde "CI/CD güvenliği" üretmiş. URL’de / ayraç olarak yorumlandığı için CI ve CD güvenliği iki ayrı segmente bölünüyor, route bulunamıyor, build patlıyor.

İlk içgüdüm “AI’a bunu yapma de” oldu. Prompt’a “tag’lerde slash kullanma” yazsam çözüm? Hayır. Çünkü AI prompt direktiflerine her zaman uymuyor — bir gün hatırlar, bir gün unutur. Insurance gibi düşünmek lazım: kapı açık kalsa da güvenlik sistemi bir adım daha vermeli.

Çözüm enforceFrontmatterLimits fonksiyonuna gömüldü:

const sanitizeItem = (s: string): string => {
  const m = s.match(/^(['"])([\s\S]*)\1$/);
  if (!m) return s;
  const cleaned = m[2]
    .replace(/[/\\?#]/g, '-')      // slash, backslash, ?, # → '-'
    .replace(/-{2,}/g, '-')         // ardışık tireleri tek tireye indir
    .trim();
  return `${m[1]}${cleaned}${m[1]}`;
};

/, \, ?, # URL’i kıran karakterler. 'CI/CD güvenliği''CI-CD güvenliği' oluyor. Build geçiyor, URL düzgün, kaldı sadece görsel bir farklılık.

Bug #2: publishDate tırnaklı string

Bir saat sonra ikinci patlama. Run “success” olarak görünüyordu ama içerik tarafına bakınca dosya boş oluşturulmuştu — sadece fallback PNG commit edilmiş, .mdx yazılmamış.

Generate logu:

Post generation failed: Error: Frontmatter validation failed:
  publishDate invalid ""2026-05-01""

Çift tırnak içinde çift tırnak görünmesinin sebebi error message’in kendisi tırnak ekliyordu. Asıl değer "2026-05-01" — yani AI tarihi tırnak içine almış.

YAML açısından publishDate: 2026-05-01 ve publishDate: "2026-05-01" ikisi de geçerli. Astro’nun Zod schema’sı z.coerce.date() ile her ikisini de kabul ediyor. Ama benim local validator’ım hayır. Validator regex’i:

if (!/^\d{4}-\d{2}-\d{2}/.test(publishDate))
  errors.push(`publishDate invalid "${publishDate}"`);

Bu regex "2026-05-01 ile başlıyor, ilk karakter " (tırnak), \d{4} matchlemiyor → fail. Validator, dosyayı git’e koymadan reject ediyor — bu davranış doğru, ama tırnaklı tarih reddedilmemeli, temizlenmeli.

Aynı enforceFrontmatterLimits’a yeni bir adım:

out = out.replace(/^(\s*publishDate:\s*)(.+)$/m, (_match, prefix, value: string) => {
  let cleaned = value.trim().replace(/^["'](.*?)["']\s*$/, '$1').trim();
  if (!/^\d{4}-\d{2}-\d{2}/.test(cleaned)) {
    if (opts.today) cleaned = opts.today;  // fallback today
  }
  return `${prefix}${cleaned}`;
});

Eğer tırnaklıysa tırnağı sökerim. Eğer bozuksa bugünün tarihiyle override ederim. AI ne yaparsa yapsın, frontmatter geçerli olarak çıkar.

Bug #3: Dosya adındaki dotted-i ile path uyumsuzluğu

En sinsisi bu oldu. Validate adımı patladı, hata:

[ImageNotFound] Could not find requested image
  ../../../assets/blog/technology/uretımda-bir-sunucu-kmesinin-thundering-herd-krizine-mudahalesi.png

Bakın bu path: uretımda — Türkçe ı (dotless i) var. Ama disk üzerinde gerçek dosya adı uretimda (ASCII i). Yani slugify() fonksiyonum başlığı ASCII’ye normalize etmiş, dosyayı doğru yazmış. Ama AI frontmatter’a coverImage yolunu yazarken orijinal Türkçe karakteri kullanmış.

İki yol farklı:

Disk:        uretimda-bir-sunucu-kmesinin-...png    (ASCII)
Frontmatter: uretımda-bir-sunucu-kmesinin-...png   (Turkish ı)

Astro build “böyle bir dosya yok” diyor, haklı.

Çözüm aşırı basit ama önemli: frontmatter’ı disk’tekiyle senkronize zorla. Generate-content.ts zaten gerçek dosya adını biliyordu (relativeImagePath). O değeri enforceFrontmatterLimits’a opsiyon olarak verip override ettiriyorum:

if (opts.expectedCoverImage) {
  const expected = opts.expectedCoverImage;
  out = out.replace(
    /^(\s*coverImage:\s*)(["'])([\s\S]*?)\2/m,
    (_match, prefix, quote, value: string) => {
      if (value !== expected) {
        console.log(`    [sanitize] coverImage "${value}" → "${expected}"`);
      }
      return `${prefix}${quote}${expected}${quote}`;
    }
  );
}

AI ne yazarsa yazsın, disk’teki gerçek dosya adı ile değiştirilir. Insurance pattern.

Tek bir konsolide normalizer

Üç bug’ın da ortak deseni var: AI içerik üretiyor, schema’mın beklediği şekle %95 yakın ama küçük bir ayrıntı kaçırıyor. Klasik “validator sıkı, generator esnek” çatışması.

Yaklaşımımı değiştirdim. Eskiden:

  1. AI yaz
  2. Validator kontrol et
  3. Hata varsa reddet, dosyayı yazma

Yeni yaklaşım:

  1. AI yaz
  2. Önce auto-fix (bilinen quirk pattern’lerini düzelt)
  3. Validator kontrol et (artık sadece truly unfixable şeyler kalır)
  4. Hata yoksa dosyayı yaz

Auto-fix’in mantığı: “AI bilerek bunu yapmadı, küçük bir slip oldu, ben de küçük bir düzeltme yapıp devam ediyorum”. Hard fail yerine soft normalization.

Tek fonksiyon, üç quirk:

function enforceFrontmatterLimits(content: string, opts: EnforceOptions = {}): string {
  // Fix #1: tag içinde URL-kıran karakterler
  // Fix #2: publishDate tırnağı + invalid format → today fallback
  // Fix #3: coverImage path → expected path zorla
  // ... ve title/description char limit'leri
}

Sonraki gün uyandığımda

Sabah pipeline-health “DEGRADED” mailim olmamıştı. Demek ki dünkü çözümler tutmuş, gece 24 yazı pipe’tan akmış. Run history’e baktım: 20 başarılı, 0 başarısız.

Bu yazıyı yazmamın asıl sebebi: bir hafta sonra başka bir AI quirk çıkacağında refleksim “yeni quirk pattern ekle” olsun, “yeni try-catch yaz” olmasın. Topluluk AI ile içerik üreten herkesin er geç bunu öğrendiğine eminim, ama bu öğrenmenin ne kadar ucuz olabileceğini paylaşmak istedim.

AI’a güveniyorsam, AI’a karşı insurance’a da güvenmeliyim.

Paylaş:

Bu yazı faydalı oldu mu?

Yükleniyor...

Bu yazı nasıldı?

ME

Mustafa Erbay

Sistem Mimarisi · Network Uzmanı · Altyapı, Güvenlik ve Yazılım

2006'dan bu yana sistem mimarisi, network, sunucu altyapıları, büyük yapıların kurulumu, yazılım ve sistem güvenliği ekseninde çalışıyorum. Bu blogda sahada karşılığı olan teknik deneyimlerimi paylaşıyorum.

Kişisel Notlar

Bu notlar sadece sizde saklanır. Tarayıcınızda yerel olarak tutulur.

Hazır 0 karakter

Yorumlar

Sunucu Taraflı AI Moderasyon

Yorumlar sunucuda yapay zeka ile denetlenir ve kalıcı olarak saklanır.

?
0/2000

Sunucu taraflı AI denetim

✉️ Ücretsiz · Spam yok · İstediğin an çık

Haftalık özet — AI değil, bizzat ben seçiyorum

Haftada bir mail: o haftanın en önemli yazısı, perde arkası notları, ve "bu hafta gerçekten kullandığım araç" bölümü. Az gürültü, çok sinyal.

  • 📌
    Haftanın en iyisi Sadece okumaya değer tek yazı
  • 🔧
    Alet çantası Bu hafta kullandığım araçlar
  • 🧠
    Perde arkası Blog'a girmeyen notlar

Spam yapmıyoruz. İstediğiniz zaman ayrılabilirsiniz. · Sadece Umami (self-hosted, Google yok) ile takip.

Okuma İstatistikleriniz

0

Yazı Okundu

0dk

Okuma Süresi

0

Gün Serisi

-

Favori Kategori

İlgili Yazılar