Headless Claude Code ve CI: `claude -p`, GitHub Actions ve Pull Request Review
Claude Code'u script'ten ve CI'dan claude -p ile çalıştırırsın; --bare proje hook'larını ve MCP server'larını dışarıda bırakır, dontAsk soru sorulacak her çağrıyı reddeder, --max-turns ve --max-budget-usd maliyeti sınırlar. Structured output ile release note taslağı, anthropics/claude-code-action@v1 ile @claude ve PR review workflow'ları, kimlik bilgisi seçenekleri, Code Review ve /code-review ultra farkı.
Sürüm notu: Bu yazı Claude Code 2.1.325'i (13 Kasım 2026) anlatır; claude -p bayrakları, GitHub Actions entegrasyonu ve review seçenekleri 15 Kasım 2026'da code.claude.com/docs'taki Run Claude Code programmatically, CLI reference, Claude Code GitHub Actions, Code Review ve Find bugs with ultrareview sayfalarından kontrol edilmiştir. Bu yazıda resmî belgelerden yararlanılmıştır. Workflow dosyaları actionlint 1.7.12 ile denetlenmiş, PowerShell script'i claude ve gh komutlarının yerine stub'lar konularak PowerShell 7.7 ile çalıştırılmıştır.
Önce Tahmin Et
Ayşe, sitedeki serilerin ortak örnek uygulaması Bookstore'da (bir online kitapçı) pull request'lerde @claude yazınca çalışan bir workflow kurdu. Hızlı ilerlemek için kendi aboneliğinden aldığı token'ı organizasyon secret'ı yaptı, --allowedTools "Bash" verdi ve bir GITHUB_TOKEN girdisi ekledi. İlk hafta iki şey dikkat çekti: Claude'un düzelttiği PR'larda (pull request kısaltması) CI hiç çalışmadı, ve bir dış katkıcının fork'tan açtığı PR'da yazdığı @claude yorumu hiçbir şey yapmadı.
Tahminin ne? Claude'un commit'leri neden CI'ı tetiklemedi? Fork'taki yorum neden çalışmadı ve bu bir hata mı? Ve token'ı organizasyon secret'ı yapmakta sorun ne? Cevaplar kurulum ve Üretimde Dikkat bölümlerinde.
claude -p: Temeller
-p (ya da --print) Claude Code'u interaktif olmayan modda çalıştırıyor: prompt'u alıyor, agent loop'u çalıştırıyor, sonucu stdout'a yazıp çıkıyor. Başarıda exit code 0, hatada sıfır dışı; script'in buna göre dallanabiliyor. Stdin'e verilen metin de prompt'un parçası oluyor:
claude -p "What does the Ordering module do?"
gh pr diff 128 | claude -p "Review this diff for EF Core query problems" --output-format json
claude -p "Summarize README.md" --bare --allowedTools "Read"
--output-format üç değer alıyor: text (varsayılan), sonucu result alanında, session ID'yi ve total_cost_usd tahminini de içeren json, ve satır satır olay akışı veren stream-json. --json-schema ile bir JSON Schema verirsen, şemaya uyan çıktı structured_output alanında geliyor; Claude isteği reddederse bu alan hiç olmuyor, script'in önce varlığını kontrol etmeli.
--bare en önemli bayrak. Belgelere göre --bare olmadan bir -p koşusu interaktif session gibi her şeyi keşfediyor: CLAUDE.md, skill'ler, subagent'lar, plugin'ler ve en önemlisi projenin .claude/settings.json'ındaki hook'lar ile .mcp.json'daki server'lar. Üstelik -p'de workspace trust dialog'u ve server onayı olmadığı için bunlar hiç güvenmediğin bir klasörde de çalışıyor. --bare bunların hiçbirini yüklemiyor; Claude'a Bash, dosya okuma ve düzenleme tool'ları kalıyor, gerekenleri --settings, --mcp-config, --agents gibi bayraklarla kendin veriyorsun. Belgeler script'ler için --bare'i öneriyor ve ileride -p'nin varsayılanı olacağını yazıyor. Bir şartı var: bare mode abonelik girişini ve sistem keychain'ini okumuyor, ANTHROPIC_API_KEY istiyor. Yedinci yazıdaki Stop hook'u gibi proje hook'larının bir script koşusuna karışmasını da --bare engelliyor.
İzinler, Tur ve Bütçe Sınırı
Belgelere göre v2.1.302'den beri claude -p, permission mode belirtilmezse Manual başlıyor ve kimsenin cevaplayamayacağı onay soruları reddediliyor. --allowedTools listedeki tool'lara sorgusuz izin veriyor, permission rule syntax'ını kullanıyor: "Bash(git diff *),Read" gibi. --permission-mode dontAsk ise soru sorulacak her çağrıyı reddediyor; okuma gibi Manual'da zaten onay istemeyen işler ve allow kurallarına uyanlar çalışıyor. Kilitli CI koşuları için belgelerin önerisi bu mod.
Koşunun ne kadar süreceğini ve harcayacağını iki bayrak sınırlıyor: --max-turns agent loop'un tur sayısını, --max-budget-usd ise tahmini harcamayı. Belgeler ikincisinin istemci tarafındaki bir tahmine baktığını, faturadan farklı olabileceğini ve harcamanın sınırı biraz geçebileceğini açıkça yazıyor; payı bırak. Bir Agent SDK uygulaması ya da --permission-prompt-tool gibi bir onay mekanizması varken bile kimse cevap veremeyecekse --permission-prompts none soruları beklemeden reddettiriyor.
Bookstore: Release Note Taslağı
Bookstore'da her sürümden önce birileri merge edilen PR'ları okuyup mağaza ekibi için sade bir değişiklik notu yazıyor. Bu, girdisi ve çıktısı belli, tool gerektirmeyen bir iş; claude -p için iyi bir aday. tools/release-note.ps1:
#Requires -Version 7.3
# Bir pull request'ten claude -p ve structured output ile CHANGELOG taslağı üretir.
# ./tools/release-note.ps1 -PullRequest 128
param(
[Parameter(Mandatory = $true)] [int]$PullRequest
)
$ErrorActionPreference = 'Stop'
$schema = @{
type = 'object'
properties = @{
title = @{ type = 'string' }
category = @{ type = 'string'; enum = @('feature', 'fix', 'maintenance') }
note = @{ type = 'string' }
}
required = @('title', 'category', 'note')
} | ConvertTo-Json -Depth 5 -Compress
$pr = gh pr view $PullRequest --json title,body | ConvertFrom-Json
$prompt = 'Write a Bookstore release note entry for store staff from the pull request on stdin. ' +
'Plain language, at most two sentences, no internal class names.'
$json = "$($pr.title)`n`n$($pr.body)" |
claude -p $prompt --bare --output-format json --json-schema $schema `
--permission-mode dontAsk --max-turns 2 --max-budget-usd 0.25
if ($LASTEXITCODE -ne 0) { throw "claude -p failed with exit code $LASTEXITCODE" }
$response = $json | ConvertFrom-Json
if (-not $response.structured_output) { throw 'No structured_output in the response.' }
$entry = $response.structured_output
Add-Content -LiteralPath 'CHANGELOG.draft.md' -Value "- [$($entry.category)] $($entry.title): $($entry.note) (#$PullRequest)"
Write-Output ("Added #{0} to CHANGELOG.draft.md, estimated cost {1} USD" -f $PullRequest, $response.total_cost_usd)
Script'teki kararları sırayla açalım. PR başlığı ve açıklaması gh ile alınıp stdin'den veriliyor; prompt sabit kalıyor, PR metni veri olarak geliyor. --bare projenin hook'larını, MCP (Model Context Protocol; dış araç ve veri kaynaklarına bağlanma protokolü) server'larını ve skill'lerini devre dışı bırakıyor; bu işin onlara ihtiyacı yok. --permission-mode dontAsk ve boş bir allow listesiyle Claude dosya okuyabiliyor ama hiçbir komut çalıştıramıyor ve hiçbir dosyayı değiştiremiyor; dosyaya yazma işini script'in kendisi yapıyor. --json-schema çıktının şeklini sabitliyor, --max-turns 2 ve --max-budget-usd 0.25 da yanlış giden bir koşunun maliyetini sınırlıyor. #Requires -Version 7.3 satırı bilinçli: içinde çift tırnak geçen JSON'u native bir programa argüman olarak doğru geçirmek için PowerShell 7.3 ve üstü gerekiyor.
--bare yüzünden script'in ANTHROPIC_API_KEY ortam değişkenine ihtiyacı var. Abonelikle çalışmak için --bare'i kaldırırsan proje hook'larının ve .mcp.json server'larının koşuya gireceğini hesaba kat; onları dışarıda bırakmak için CLI reference'taki --setting-sources ve --strict-mcp-config bayraklarına bak.
GitHub Actions: Kurulum ve Kimlik Bilgileri
anthropics/claude-code-action@v1, Claude Code'u bir GitHub Actions job'ının içinde çalıştırıyor. En kısa yol Claude Code içinde /install-github-app: Claude GitHub App'i kuruyor, secret'ı ekliyor ve workflow dosyalarıyla bir pull request hazırlıyor. Repo'da admin yetkisi, kurulu ve giriş yapılmış gh CLI'ı ve github.com'da bir repo gerekiyor. Elle kurulumda App'i kendin kuruyor, secret'ı ekliyor ve workflow dosyasını kopyalıyorsun.
Kimlik bilgisi için üç seçenek var. ANTHROPIC_API_KEY, Claude Console'dan alınan bir API key. CLAUDE_CODE_OAUTH_TOKEN, claude setup-token ile üretilen ve bir Claude aboneliğine bağlı uzun ömürlü token. Workload identity federation ise workflow'un GitHub OIDC (OpenID Connect; kısa ömürlü kimlik token'ı veren standart) token'ını bir Claude Console service account'u üzerinden API erişimine çeviriyor; repo'da uzun ömürlü hiçbir secret durmuyor. Tahmindeki üçüncü sorunun cevabı belgelerde açık: birden fazla repo'nun paylaştığı bir secret için OAuth token değil API key kullan, çünkü OAuth token onu üreten kişinin aboneliğine bağlı. Kurumsal kurulumda ilk tercih federation.

Bookstore: @claude Workflow'u
Action iki modda çalışıyor. prompt girdisi yoksa interactive mode: Claude bir issue ya da PR yorumundaki @claude'u bekliyor ve sonucu aynı yere yorum olarak yazıyor. prompt verilirse automation mode: her tetiklemede doğrudan çalışıyor. .github/workflows/claude.yml:
name: Claude
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
concurrency:
group: claude-${{ github.event.issue.number || github.event.pull_request.number }}
cancel-in-progress: false
jobs:
claude:
if: contains(github.event.comment.body, '@claude')
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 1
- uses: actions/setup-dotnet@v6
with:
dotnet-version: 10.0.x
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--max-turns 15
--allowedTools "Bash(dotnet build *)"
permissions bloğu belgelerdeki örnekle aynı ve her satırın sebebi var: dosya değiştirmek ve PR'a yazmak için contents, pull-requests ve issues yazma yetkisi; App kimlik doğrulaması için id-token: write; CI sonuçlarını okumak için actions: read. Bizim eklediklerimiz maliyet ve kontrolle ilgili: --max-turns 15, job için timeout-minutes: 30 ve aynı PR'da üst üste yazılan yorumları sıraya koyan concurrency. --allowedTools'ta yalnız dotnet build var: Bookstore'un API testleri SQL Server istediği için onları bu job'da değil, GitHub Actions serisindeki CI workflow'unda çalıştırıyoruz.
Tahmindeki ilk sorunun cevabı Troubleshooting bölümünde: GitHub, varsayılan GITHUB_TOKEN ile atılan commit'lerde workflow tetiklemiyor. Action'a github_token: ${{ secrets.GITHUB_TOKEN }} verirsen Claude'un commit'leri CI'ı çalıştırmıyor; girdiyi kaldırıp Action'ın Claude GitHub App olarak kimlik doğrulamasına izin ver. GitHub tarafındaki secret ve GITHUB_TOKEN permission'larının ayrıntısı GitHub Actions serisinin sekizinci yazısında.
Bookstore: Pull Request Review Workflow'u
İkinci workflow her PR açıldığında ya da güncellendiğinde bir review yapıyor. Belgelerdeki örnek gibi Anthropic'in resmî marketplace'indeki code-review plugin'ini kurup skill'ini prompt olarak çağırıyoruz:
name: Claude Review
on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
concurrency:
group: claude-review-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
review:
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
plugin_marketplaces: "https://github.com/anthropics/claude-plugins-official.git"
plugins: "code-review@claude-plugins-official"
prompt: "/code-review:code-review --comment ${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
claude_args: '--max-turns 30 --allowedTools "mcp__github_inline_comment__create_inline_comment"'
--comment bulguları PR'a satır yorumu olarak yazdırıyor; bulgu yoksa tek bir özet yorum geliyor. claude_args'taki --allowedTools satırı gereksiz görünse de belgelere göre şart: Action satır yorumu atan MCP server'ını ancak bu tool --allowedTools'ta adıyla geçiyorsa başlatıyor. Job'ın kendi yetkisi salt okuma; yorumlar Claude GitHub App üzerinden gidiyor. Claude taslak ve kapalı PR'ları, kendi yorumunun zaten olduğu PR'ları ve gerek görmediği önemsiz değişiklikleri atlıyor. --max-turns 30 bizim seçtiğimiz bir sınır; büyük PR'larda yetmezse loglardan görüp ayarla.
Plugin'lerin ve marketplace'lerin kendisi on birinci yazıda; burada plugins girdisiyle job'a özel kurulan bir paket olarak kullanıyoruz.
Code Review ve /code-review ultra
Workflow yazmadan review almanın iki yolu daha var ve ikisi de farklı ürünler. Code Review, Team ve Enterprise planlarında yöneticinin açtığı yönetilen bir servis: her PR'ı birden fazla agent'la inceliyor, bulguları doğrulayıp satır yorumu ve bir check run olarak yazıyor, PR'ı hiçbir zaman onaylamıyor ya da engellemiyor. Kullanımı ayrı faturalanıyor. /code-review ultra ise terminalden başlatılan, cloud'da çalışan ve bulguların her birini ayrıca doğrulayan derin bir review; belgelere göre research preview aşamasında. Pro, Max ve Ultra hesaplarında bir kerelik üç ücretsiz koşu var, sonrası usage credit olarak faturalanıyor ve tipik bir review 5 ile 25 dolar arasında tutuyor. Yerel /code-review ise beşinci yazıda gördüğümüz bundled skill.
Üretimde Dikkat
Kimin tetikleyebildiğini bil. Action, issue ve PR olaylarında tetikleyen kullanıcının repo'da yazma yetkisi olmasını istiyor ve bot aktörleri allowed_bots'ta listelenmedikçe reddediyor; bu ikinci kontrol bot'ların birbirini döngüye sokmasını engelliyor. Tahmindeki ikinci sorunun cevabı bu ilk kontrol: dış katkıcının repo'da yazma yetkisi yok, Action koşuyu bu yüzden reddediyor. Bu bir hata değil, bilinçli bir güvenlik sınırı; belirli kullanıcılara izin vermek için allowed_non_write_users var. Fork'larla ilgili ikinci bir sınır da GitHub tarafında: public bir repo'da fork'tan gelen PR'larla tetiklenen koşulara GitHub secret'ları vermiyor, review workflow'u bu yüzden yalnız aynı repo'daki branch'lerden açılan PR'larda çalışıyor.
Issue ve PR metni Claude için veri, talimat değil; ama model bunu her zaman ayırt edemiyor. Yazma yetkisi olan bir workflow'a Bash gibi geniş bir allow vermek, bir yorumdaki kötü niyetli talimata geniş bir yüzey açmak demek. Prompt injection'ın derin anlatımı LLM Uygulama Güvenliği serisinde, pipeline'da kimin neyi değiştirdiğinin izlenmesi CI/CD Temelleri serisinin yedinci yazısında.
Maliyeti sınırla: her job'a timeout-minutes, her koşuya --max-turns, aynı PR için concurrency. Repo'nun CLAUDE.md'si her koşuda okunuyor; kısa tutmak da maliyeti düşürüyor. API key ve token'ları yalnız GitHub secret'ı olarak tut, workflow dosyasına yazma.
Sık Düşülen Tuzaklar
Geniş --allowedTools. "Bash" her komuta izin veriyor; ihtiyaç duyulan komutları tek tek yaz.
Timeout ve concurrency olmadan çalışan job. Takılan bir koşu, varsayılan job süresi dolana kadar runner dakikası ve token harcıyor.
Kişisel OAuth token'ını organizasyon secret'ı yapmak. Token bir kişinin aboneliğine bağlı; o kişi ayrılınca bütün repo'lardaki workflow'lar duruyor. Paylaşılan secret için API key ya da federation.
--bare olmadan script çalıştırmak. Proje hook'ları ve MCP server'ları her koşuda devreye giriyor; güvenmediğin bir repo'da bu, onların kodunu senin yetkinle çalıştırmak demek.
Küçük Bir Deneme
★☆☆ Kendi bilgisayarında claude -p'yi bir kez --bare ile, bir kez onsuz aynı prompt'la çalıştır ve --output-format json çıktısındaki total_cost_usd değerlerini karşılaştır.
★★☆ release-note.ps1'i son on PR için döngüyle çalıştır, CHANGELOG.draft.md'yi oku ve şemaya bir breaking alanı ekle. Hangi PR'larda Claude'un bu alanı yanlış doldurduğunu not et.
★★★ Bir test repo'sunda claude.yml'ı kur ve aynı PR'a art arda üç @claude yorumu yaz. concurrency grubunun koşuları nasıl sıraya koyduğunu Actions sekmesinden izle, sonra cancel-in-progress'i true yapıp farkı kaydet.
Bu yazıdaki her şey tek bir repo'ya özeldi: hook'lar, subagent'lar, MCP ayarı ve workflow'lar. On birinci yazıda bunları ekip için bir plugin'de paketleyecek, output style'lara ve organizasyonun dayatabileceği managed settings'e geçeceğiz.