Copilot CLI: Terminalde Agent, Plan Mode, Sandbox, `copilot -p` ile Otomasyon ve ACP
copilot -p'nin exit code'u 0 olsa da cevap yanlış olabilir: Bookstore'da yerel modelle iki koşuda da 0 geldi, biri işe yaramaz bir JSON parçası, diğeri commit'te olmayan maddeler yazdı. Kurulum, plan mode, --allow-tool ve --deny-tool kuralları, sandbox ve --cloud, GITHUB_TOKEN ve copilot-requests ile GitHub Actions, actionlint'in tanımadığı izin, BYOK ve ACP istemcisi.
Sürüm notu: Bu yazıdaki komutlar GitHub Copilot CLI 1.0.112 ile, kodlar .NET 10 ile denenmiştir; workflow dosyası actionlint 1.7.12 ile doğrulanmıştır. CLI GitHub hesabı olmadan, COPILOT_OFFLINE=true ile ve yerel bir Ollama modeliyle (qwen3:4b) çalıştırılmıştır; gösterilen agent çıktıları bu küçük modelden gelir, Copilot'un kendi modellerinden değil. Plan mode, sandbox, --cloud, /delegate ve GitHub Actions'taki kimlik doğrulama 15 Kasım 2026'da docs.github.com'dan ve CLI'ın kendi copilot help konularından alınmıştır.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için her main commit'inde sürüm notu taslağı yazan bir CI adımı istiyorsun: copilot -p "summarize the last commit in three bullet points". Adım çalışıyor, exit code 0, çıktı dosyası oluşuyor.
Tahminin ne? Exit code 0, Copilot'un istediğin işi yaptığını gösterir mi? Adım hangi komutları onaysız çalıştırabilir? Workflow'a ne kadar yetki vermek gerekir? Bu yazıda copilot -p'yi yerel modelle iki kez çalıştırdık; cevap ilk koşuda.
Kurulum ve İlk Oturum
Copilot CLI Linux, macOS ve Windows'ta çalışıyor; Windows'ta PowerShell'de ya da WSL'de (Windows Subsystem for Linux; Windows içinde Linux çalıştıran katman). Windows'ta PowerShell 6 ya da üstü gerekiyor; kurulum WinGet, npm (Node.js 22 ve üstü), macOS ve Linux'ta Homebrew ya da kurulum script'iyle yapılıyor:
PS> winget install GitHub.Copilot
PS> npm install -g @github/copilot@1.0.112
PS> copilot --version
GitHub Copilot CLI 1.0.112.
Run 'copilot update' to check for updates.
CLI kendini varsayılan olarak güncelliyor. CI'da ve sürümü sabit tutmak istediğin yerlerde COPILOT_AUTO_UPDATE=false ya da --no-auto-update kullan; CLI, CI gibi environment variable'ları görünce otomatik güncellemeyi zaten kapatıyor.
copilot yazınca interactive oturum açılıyor. İlk açılışta CLI, başlattığın klasöre ve altına güvenip güvenmediğini soruyor; altıncı yazıda gördüğümüz gibi güvenilmeyen bir klasördeki .mcp.json, skill ve hook dosyaları yüklenmiyor. Doküman bu kapsamın sezgisel olduğunu ve güvenilen klasör dışındaki her dosyanın korunacağını garanti etmediğini açıkça yazıyor. Oturumda Shift+Tab modlar arasında dolaşıyor: varsayılan mod, plan mode ve autopilot. Plan mode'da Copilot önce soru sorup bir uygulama planı çıkarıyor ve kod yazmıyor; üçüncü yazıdaki plan önce, kod sonra akışının terminaldeki karşılığı bu. Aynı modları --mode plan ya da --plan ile başlangıçta seçebilirsin.
Tool Onayları ve İzin Kuralları
CLI shell komutu çalıştırmadan, dosya değiştirmeden, izinli yolların ve URL'lerin dışına çıkmadan ve MCP (Model Context Protocol; agent'ın dış araçlara bağlandığı protokol) tool'u çağırmadan önce soruyor; yalnız okuma yaptığını tanıdığı komutları sormadan çalıştırıyor. Onay penceresinde "bu repoda bir daha sorma" seçeneği kararı ~/.copilot/permissions-config.json'a yazıyor. Komut satırında ise kurallar o çalıştırma için geçerli:
--allow-tool='shell(git:*)'bütün git komutlarını,--allow-tool='shell(git show:*)'yalnızgit show'u sormadan çalıştırır.dotnet,npm,ghgibi araçlar alt komutlarıyla eşleşiyor.--deny-tool='shell(git push)'ve--deny-tool='write(.env)'her izinden güçlü;--allow-allve--yoloda onları aşamıyor.--available-toolsve--excluded-toolsizin değil, modelin hangi tool'ları görebileceğini belirliyor.
Yardım metnindeki bir uyarı önemli: shell(gh pr) izni yalnız ilk alt komutu tanıdığı için gh pr merge ve gh pr close'u da onaysız bırakıyor; shell kuralları yalnız gh pr create'e izin veremiyor. Bu tür bir sınır için altıncı yazıdaki preToolUse hook'u gerekiyor.
Sandbox ve Cloud Oturumları
Onay kuralları "ne çalışabilir" sorusunu cevaplıyor; sandbox ise "çalışan komut neye erişebilir" sorusunu. copilot help sandbox'a göre komut sandbox'ı varsayılan olarak kapalı: kapalıyken shell komutları senin kullanıcı hesabının bütün yetkileriyle çalışıyor. Oturumda /sandbox enable ya da tek oturum için --sandbox ile açılıyor ve Microsoft Execution Containers (MXC) üzerinden işletim sisteminin kendi izolasyonunu kullanıyor; Windows'ta bunun için bir container mekanizması, Linux'ta bubblewrap. İzin verilen yolların dışına çıkan komut sistemi etkilemek yerine hata veriyor. Bir sınırı açıkça yazılmış: sandbox içindeki komut, sabit bir engel listesi dışında shell environment'ını devralıyor; environment'taki başka bir credential (bulut sağlayıcısının anahtarı gibi) maskelemeyi ayarlamadıkça görünür kalıyor. Organization'ın yönetilen policy'si sandbox'ı zorunlu kılabiliyor.
İşi makineden tamamen çıkarmak için iki yol var. copilot --cloud oturumu GitHub'ın barındırdığı bir cloud sandbox'ta başlatıyor; oturumu başka bir Copilot istemcisinden izleyip yönlendirebiliyorsun. /delegate (ya da başına & konan bir prompt) ise görevi yedinci yazıdaki cloud agent'a gönderiyor: commit edilmemiş değişiklikleri yeni bir branch'te checkpoint olarak commit etmeyi teklif ediyor, agent draft pull request açıyor. Not: 1.0.112'nin copilot --help çıktısında ne --cloud ne de --sandbox listeleniyor; ikisi de dokümanda ve help sandbox konusunda geçiyor. Sürümünde var olup olmadığını denemeden önce dokümana bak.
copilot -p ile Script ve CI
-p prompt'u çalıştırıp çıkıyor; -s yalnız agent'ın cevabını basıyor. Non-interactive modda onay sorulamadığı için önceden izin verilmeyen her tool çağrısı reddediliyor. Bookstore'da yerel modelle şu komutu çalıştırdık:
$ export COPILOT_OFFLINE=true COPILOT_PROVIDER_BASE_URL=http://127.0.0.1:11434/v1 COPILOT_MODEL=qwen3:4b
$ copilot -p "Use git show --stat HEAD to summarize the last commit in at most three bullet points." \
-s --no-ask-user --available-tools bash \
--allow-tool='shell(git show:*)' --deny-tool='shell(git push)' --usage-output-file usage.json
{
"command": "dotnet run eng/check-instructions.cs"
}
$ echo $?
0
$ copilot -p "Run the shell command git show --stat HEAD, then summarize that commit in at most three bullet points." \
-s --no-ask-user --available-tools bash \
--allow-tool='shell(git show:*)' --deny-tool='shell(git push)' --usage-output-file usage.json
The commit successfully aligns with the project's rules:
- All changes to code-review instructions (`code-review.instructions.md`, `SKILL.md`, `main-review.json`, and `.github/skills` artifacts) avoid secrets/connection strings
- No module-level database interaction violations (rules applied to infrastructure)
- New code reviews follow `TypedResults`/`ProblemDetails` error handling pattern
- 136 lines of instructions maintain UTC time alignment and `decimal` currency rules
- Checks are now consistent via `eng/check-instructions.cs` (exit code 0 confirms validation)
No additional changes required to `AGENTS.md` since the instructions file reconciliation is already resolved. ✅
$ jq -c '.modelMetrics' usage.json
{"qwen3:4b":{"requests":{"count":2,"cost":0},"usage":{"inputTokens":4100,"outputTokens":3146,"cacheReadTokens":8,"cacheWriteTokens":0},"totalNanoAiu":0,"aiCreditsStatus":"unavailable"}}
İki koşuda da model önce izin verilen git show --stat HEAD komutunu çalıştırdı; oturum kaydında komut ve çıktısı görünüyor. İlk koşuda cevap olarak üç madde yerine, talimat dosyalarından alınmış gibi duran bir JSON parçası bastı ve CLI 0 ile çıktı. İkinci koşuda prompt'u biraz değiştirdik: bu kez madde işaretli bir cevap geldi, ama üç yerine beş madde vardı ve bir kısmı commit'te olmayan şeyler söylüyordu. Commit'in eklediği 136 satır gerçek, "UTC (eşgüdümlü evrensel saat) ve decimal kurallarını koruyan 136 satır talimat" değil; TypedResults ile ilgili madde de commit'le ilgisiz. Önce Tahmin Et'in cevabı bu: exit code 0 iki koşuda da geldi.
Exit code yalnız oturumun hatasız bittiğini söylüyor, cevabın doğru olduğunu değil. --usage-output-file'ın yazdığı JSON da bunu destekliyor: bu koşuda iki model isteği ve token sayıları var, ama ne yapıldığına dair bir sinyal yok. Kendi model sağlayıcınla çalışınca dosyada aiCreditsStatus değeri unavailable; GitHub bu modeller için ücret almıyor ve kota göstermiyor.
GitHub Actions'ta aynı işi yapan workflow. Doküman organization repolarında kişisel token yerine GITHUB_TOKEN kullanmayı öneriyor; bunun için workflow'a copilot-requests: write izni verilmeli ve organization'da "Allow use of Copilot CLI billed to the organization" policy'si açık olmalı:
name: Copilot commit summary
on:
push:
branches: [main]
# GITHUB_TOKEN yalnız kodu okuyabilir ve Copilot isteği yapabilir; push edemez, yorum yazamaz.
permissions:
contents: read
copilot-requests: write
jobs:
summary:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 2
- uses: actions/setup-node@v7
with:
node-version: 24
- name: Install Copilot CLI
run: npm install -g @github/copilot@1.0.112
- name: Summarize the commit
env:
GITHUB_TOKEN: ${{ github.token }}
COPILOT_AUTO_UPDATE: "false"
run: |
copilot -p "Run git show --stat HEAD and summarize this commit for the release notes in at most five bullet points." \
-s --no-ask-user \
--available-tools bash view \
--allow-tool='shell(git show:*)' --deny-tool='shell(git push)' \
--max-ai-credits 30 \
--usage-output-file usage.json > summary.md
# copilot -p işe yaramaz bir cevapta da 0 ile çıkabilir; çıktıyı kullanmadan önce kontrol et.
if ! grep -q '^[-*] ' summary.md; then
echo "::error::The summary has no bullet points"
exit 1
fi
cat summary.md >> "$GITHUB_STEP_SUMMARY"
jq '{requests: .modelMetrics | map_values(.requests.count)}' usage.json
Workflow'un savunma katmanları: token yalnız okuma ve Copilot isteği yapabiliyor, model yalnız bash ve view'u görüyor, onaysız çalışabilen tek komut git show, oturum en fazla 30 AI Credits harcayabiliyor (CLI'ın kabul ettiği en küçük sınır) ve çıktı kullanılmadan önce biçimi kontrol ediliyor. Bu kontrol yukarıdaki ilk cevabı durdururdu, ikincisini durdurmazdı; biçim kontrolü içerik doğrulaması değil, kaba bir filtre. actionlint'ten geçirdiğimizde ise ilginç bir sonuç çıktı:
$ actionlint .github/workflows/copilot-commit-summary.yml
.github/workflows/copilot-commit-summary.yml:10:3: unknown permission scope "copilot-requests". all available permission scopes are "actions", "artifact-metadata", "attestations", "checks", "contents", "deployments", "discussions", "id-token", "issues", "models", "packages", "pages", "pull-requests", "repository-projects", "security-events", "statuses" [permissions]
|
10 | copilot-requests: write
| ^~~~~~~~~~~~~~~~~
actionlint 1.7.12 (15 Kasım 2026'daki son sürüm) copilot-requests iznini henüz tanımıyor. İzni silmek yerine yalnız bu dosya için bu uyarıyı yok sayan bir actionlint ayarı ekledik:
# actionlint 1.7.12 copilot-requests permission'ını henüz tanımıyor (docs.github.com'da belgeli).
paths:
.github/workflows/copilot-commit-summary.yml:
ignore:
- 'unknown permission scope "copilot-requests"'
GitHub bu senaryoda doğrudan copilot çağırmak yerine GitHub Agentic Workflows'u öneriyor; varsayılan olarak GITHUB_TOKEN kullanıyor ve otomasyon için ek korumalar getiriyor. Doğrudan çağırıyorsan dokümanın uyarısını ciddiye al: CLI workflow ortamına geniş erişimle çalışıyor ve fork'tan gelen pull request'lerle tetiklenen workflow'lar özellikle riskli.
Repoyu Genişleten Dosyalar CLI'da
Dördüncü ve altıncı yazıda yazdığımız dosyaların hepsi CLI'da da çalışıyor ve login gerektirmeyen komutlarla kontrol edilebiliyor: talimatlar copilot instruction list, MCP server'ları copilot mcp list ve copilot mcp get, skill'ler copilot skill ile. Custom agent'ı --agent migration-reviewer seçiyor; hook'lar .github/hooks/*.json'dan ve ~/.copilot/hooks'tan yükleniyor. Copilot Memory için copilot memories komutu var; prompt mode'da memory varsayılan olarak kapalı ve --enable-memory ile açılıyor.
Kendi model sağlayıcını bağlamak da mümkün. COPILOT_PROVIDER_BASE_URL OpenAI uyumlu bir endpoint'i (Ollama, vLLM, Foundry Local) gösteriyor ve CLI yardım metnine göre bu durumda GitHub girişi gerekmiyor; CLI makinede 127.0.0.1:11434'teki Ollama'yı kendisi de buluyor. COPILOT_OFFLINE=true bütün ağ erişimini kapatıyor: GitHub girişi, web tool'ları, GitHub MCP server'ı, cloud agent'a devretme, telemetri ve otomatik güncelleme. Bu yazıdaki bütün koşular böyle yapıldı. Bir sınır: cloud agent, code review, remote oturumlar ve cloud sandbox GitHub'ın sunucularında çalıştığı için kendi modelini değil GitHub'ın modellerini kullanıyor.
ACP ile Başka Araçlarda Copilot
ACP (Agent Client Protocol; editörlerin ve araçların bir coding agent'la konuştuğu açık protokol), Copilot CLI'ı başka bir istemcinin agent'ı yapıyor. Zed ya da bir JetBrains IDE'si gibi ACP destekleyen editörlerde komut olarak copilot --acp kaydediliyor; editör CLI'ı başlatıp stdio üzerinden satır satır JSON-RPC mesajlarıyla konuşuyor. Özellik 15 Kasım 2026 itibarıyla public preview. Protokolün nasıl göründüğünü görmek için küçük bir C# istemcisi yazdık:
// Kısaltılmış; Request() tek bir JSON satırı yazar, sonra eşleşen cevap gelene kadar satır okur.
ProcessStartInfo start = new(agent, "--acp --stdio --available-tools=view,glob")
{
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
};
using Process copilot = Process.Start(start)!;
int nextId = 1;
JsonNode initialize = await Request("initialize", new JsonObject
{
["protocolVersion"] = 1,
["clientCapabilities"] = new JsonObject { ["fs"] = new JsonObject { ["readTextFile"] = false, ["writeTextFile"] = false } },
});
Console.WriteLine($"agent: {initialize["agentInfo"]?["name"]} {initialize["agentInfo"]?["version"]}, protocol {initialize["protocolVersion"]}");
Console.WriteLine($"auth methods: {string.Join(", ", initialize["authMethods"]?.AsArray().Select(m => m?["id"]?.ToString()) ?? [])}");
JsonNode session = await Request("session/new", new JsonObject { ["cwd"] = Environment.CurrentDirectory, ["mcpServers"] = new JsonArray() });
string sessionId = session["sessionId"]!.ToString();
Console.WriteLine($"session: {sessionId[..8]}...");
JsonNode result = await Request("session/prompt", new JsonObject
{
["sessionId"] = sessionId,
["prompt"] = new JsonArray(new JsonObject { ["type"] = "text", ["text"] = prompt }),
});
Console.WriteLine($"\nstop reason: {result["stopReason"]}");
copilot.StandardInput.Close();
await copilot.WaitForExitAsync();
$ export COPILOT_OFFLINE=true COPILOT_PROVIDER_BASE_URL=http://127.0.0.1:11434/v1 COPILOT_MODEL=qwen3:4b
$ dotnet run eng/acp-client.cs "Use the glob tool to list the .cs files in the eng folder, then answer with their names only."
agent: Copilot 1.0.112, protocol 1
auth methods: copilot-login
session: 75630f81...
Info: Disabled tools: bash, bookstore-get_book, bookstore-list_institutions, bookstore-search_books, create, edit, grep, playwright-browser_click, ..., sql, task, write_agent
[tool] Finding files matching eng/**/*.cs (read)
acp-client.cs
review-context.cs
context-size.cs
check-instructions.cs
mcp-smoke.cs
ai-credits.cs
stop reason: end_turn
Sırayla: initialize agent'ın adını, sürümünü ve kimlik doğrulama yöntemini (copilot-login) döndürüyor; session/new bir oturum açıyor; session/prompt sürerken agent session/update bildirimleriyle tool çağrılarını ve cevabın parçalarını gönderiyor. Yardım metnine göre ne giriş ne de kendi model sağlayıcısı varken CLI ACP oturumu açmayı reddediyor; biz yerel modelle açabildik. Sunucuyu başlatırken verdiğimiz --available-tools=view,glob her oturuma uygulanıyor; session/new bunu değiştiremiyor ve "Disabled tools" satırı geri kalan her şeyi listeliyor. Bu satırda Playwright'ın yalnız altıncı yazıda .mcp.json'da açtığımız beş tool'u görünüyor; yani tools listesi CLI'da gerçekten uygulanıyor.
Kısa bir eşleme tablosu; ayrıntılar Claude Code ve OpenAI Codex serilerinde:
| İş | Copilot CLI | Claude Code | Codex CLI |
|---|---|---|---|
| Proje talimatı | AGENTS.md, .github/copilot-instructions.md | CLAUDE.md | AGENTS.md |
| Script'ten çalıştırmak | copilot -p | claude -p | codex exec |
| Proje MCP ayarı | .mcp.json | .mcp.json | config.toml |
| Skill klasörü | .github/skills, .claude/skills, .agents/skills | .claude/skills | .agents/skills |
| Komut izolasyonu | /sandbox, --cloud | Sandbox ve permission mode'ları | Sandbox modları ve approval policy |
Üretimde Dikkat
CI'da otomatik onay modunu kullanma. --yolo ya da COPILOT_ALLOW_ALL=true bütün tool'ları onaylıyor ve tam olarak "true" yazıldığında çalışma klasörüne de güveniyor; bu da o klasördeki hook'ların ve MCP server'larının yüklenmesi demek. Bir pull request'ten gelen kod, kendi hook'unu senin CI token'ınla çalıştırabilir.
Secret'ları gizle. --secret-env-vars verilen environment variable'ları shell ve MCP server ortamından çıkarıyor; yardım metnine göre değer başka bir yoldan tool sonucuna ulaşırsa yine modele gidiyor ve oturum geçmişine yazılıyor.
Oturum sınırı koy. --max-ai-credits yumuşak bir sınır: kullanım cevap gelince bilindiği için tek bir cevap sınırı aşabiliyor, sonraki model çağrısı engelleniyor.
Sürümü sabitle. Workflow'da @github/copilot@1.0.112 ve COPILOT_AUTO_UPDATE=false kullandık; aksi hâlde aynı workflow iki gün arayla farklı bir CLI ile çalışabilir.
Sık Düşülen Tuzaklar
CI'da geniş yetkili token'la otomatik onay modunda çalıştırmak. contents: write ve --yolo birlikteyse prompt injection içeren bir commit mesajı bile repoya yazabilir.
Exit code 0'ı başarı saymak. Çıktının biçimini ve içeriğini kontrol eden bir adım koy.
shell(gh pr) gibi geniş kurallar. İlk alt komutla eşleştiği için beklemediğin komutları da onaysız bırakır.
Sandbox'ı secret koruması sanmak. Sandbox environment'ı devralıyor; maskelenmeyen credential'lar görünür.
Dokümandaki bir seçeneğin her sürümde olduğunu varsaymak. CLI sık sürüm çıkarıyor; copilot --help ve copilot help <konu> kurulu sürümün gerçeğini söylüyor.
Küçük Bir Deneme
★☆☆ Kendi reponda copilot help permissions'ı oku ve günlük işin için bir --allow-tool ve --deny-tool listesi yaz. dotnet ef database update hangi kuralla engellenir?
★★☆ Bookstore'daki copilot-commit-summary.yml'a, özette commit'te değişmeyen bir dosya adı geçiyorsa adımı kıran bir kontrol ekle (git show --name-only HEAD ile karşılaştır).
★★★ eng/acp-client.cs'i, agent'ın session/request_permission isteklerini konsolda soracak şekilde değiştir; --available-tools'a bash ekleyip bir build komutu iste ve hangi seçeneklerin (optionId, kind) geldiğini kaydet.
Şimdiye kadar Copilot'u tek bir geliştiricinin gözünden kullandık. Serinin son yazısında ekibe ve kuruma bakıyoruz: policy'ler, content exclusion, model erişimi, kullanım metrikleri, AI Credits bütçesi ve Copilot, Claude Code ve Codex arasında seçim.