Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.
01Apa itu Codex SDK dan apa bedanya dengan Codex CLI?
Codex SDK adalah library untuk menjalankan agent Codex dari kode Anda sendiri. Package TypeScript @openai/codex-sdk membungkus codex CLI: ia men-spawn codex exec dan bertukar event JSONL lewat stdin dan stdout. Agent-nya sama dengan CLI, tetapi digerakkan oleh script, sehingga cocok untuk job CI dan tool internal.
02Bagaimana cara menginstal Codex SDK untuk TypeScript dan Python?
Untuk TypeScript jalankan npm install @openai/codex-sdk, yang membutuhkan Node.js 18 atau lebih baru dan hanya berjalan di server-side. Untuk Python jalankan pip install openai-codex, yang membutuhkan Python 3.10 atau lebih baru. Keduanya memakai ulang autentikasi Codex yang sudah ada, dan client TypeScript juga menerima opsi apiKey yang diteruskan ke CLI sebagai CODEX_API_KEY.
03Bagaimana cara melanjutkan thread Codex di lain waktu?
Baca thread.id setelah turn pertama dimulai, atau ambil thread_id dari event thread.started saat streaming, lalu simpan. Nanti panggil codex.resumeThread(threadId) dan lanjutkan dengan run(). Thread disimpan di disk lokal dalam folder sessions Codex, jadi resume harus dilakukan di mesin yang memiliki folder tersebut.
04Sandbox mode apa yang sebaiknya dipakai untuk Codex di CI?
Pakai read-only untuk triage, review, dan penjelasan, dan workspace-write ketika agent perlu menyusun fix di dalam checkout. Hindari danger-full-access di runner CI karena runner menyimpan token dan deploy key. Set approvalPolicy ke never untuk run tanpa pengawasan, karena script tidak bisa menjawab prompt approval.
Jika Anda mem-pipe npm test ke tee tanpa mendeklarasikan shell, GitHub Actions menjalankan bash -e tanpa pipefail, sehingga step melaporkan exit code dari tee, yaitu nol. Tambahkan shell: bash pada step tersebut, yang menjalankan bash dengan -eo pipefail. Setelah itu if: failure() terpicu dan step triage Codex benar-benar berjalan.
Tutorial Codex SDK: Otomatisasi Coding dengan TypeScript
Tutorial Codex SDK praktis: memulai dan melanjutkan thread dari TypeScript, memilih sandbox mode, streaming event, dan memasang Codex di job GitHub Actions yang men-triage test gagal lalu menyusun fix.
Codex SDK (@openai/codex-sdk untuk TypeScript, openai-codex untuk Python) menjalankan agent Codex dari kode Anda sendiri dengan men-spawn Codex CLI. Mulai thread, panggil run() atau runStreamed(), set sandboxMode dan approvalPolicy secara eksplisit, lalu minta JSON lewat outputSchema agar job CI bisa bertindak atas hasilnya tanpa mem-parsing prosa.
Pertama kali saya ingin Codex berjalan di dalam pipeline, bukan di terminal, pertanyaannya sangat konkret: sebuah pull request baru saja merah, belum ada yang melihatnya, dan log-nya empat ratus baris output instalasi dengan satu stack trace di paling bawah. Bisakah sebuah agent membaca log itu, membuka kodenya, menjelaskan apa yang rusak, lalu berhenti di situ kecuali saya minta lebih? Codex SDK adalah bagian yang membuat hal itu menjadi script, bukan sesi chat.
Tutorial Codex SDK ini membangun job tersebut dengan TypeScript: thread pertama, pengaturan sandbox dan approval yang penting di CI, streaming event, hasil triage terstruktur, workflow GitHub Actions, dan thread yang dilanjutkan untuk menyusun fix. Setiap nama opsi di bawah diambil dari type definition SDK di repository openai/codex dan dari dokumentasi SDK OpenAI, bukan dari ingatan. Ini panduan membangun; untuk memilih antara Codex dan Claude Code sebagai alat harian, post perbandingan di situs ini sudah membahasnya.
Apa sebenarnya Codex SDK itu
SDK ini bukan HTTP API baru. Package TypeScript-nya membungkus codex CLI dari @openai/codex: ia men-spawn codex exec dengan output JSON eksperimental dan bertukar event JSONL lewat stdin dan stdout. Satu keputusan desain itu menjelaskan sebagian besar perilakunya. SDK hanya untuk server-side, butuh filesystem dan working directory, mewarisi konfigurasi CLI, dan thread tersimpan di disk lokal dalam folder sessions di bawah direktori home Codex.
Aspek
TypeScript
Python
Package dan instalasi
npm install @openai/codex-sdk
pip install openai-codex
Runtime
Node.js 18 atau lebih baru
Python 3.10 atau lebih baru, dengan runtime Codex CLI yang di-pin
Memulai dan melanjutkan
startThread(), lalu run() berulang kali; resumeThread(id)
thread_start(), lalu run() berulang kali
Gaya async
Promise, ditambah runStreamed() sebagai async generator
Client Codex sinkron, atau AsyncCodex dengan async with
Pengaturan sandbox
String sandboxMode di thread options
Enum Sandbox, bisa di-override per run()
Karena SDK menggerakkan CLI, agent benar-benar bekerja di mesin tempat ia berjalan: menjalankan perintah shell, menerapkan patch, dan membaca file di working directory. Justru itu tujuannya, dan itu juga alasan bagian sandbox di bawah wajib dibaca.
Cek versi sebelum Anda pin: saat tulisan ini dibuat, npm registry mencantumkan @openai/codex-sdk 0.159.3, yang bergantung pada @openai/codex dengan versi yang persis sama. Upgrade keduanya bersamaan dengan meng-upgrade SDK, jangan pernah CLI-nya saja, atau bentuk event JSONL yang di-parse SDK bisa bergeser dari yang dikeluarkan CLI.
Tutorial Codex SDK: thread pertama Anda di TypeScript
Thread adalah satu percakapan dengan agent. Setiap panggilan run() adalah satu turn: agent membuat rencana, menjalankan perintah, mengedit file bila diizinkan, lalu selesai dengan pesan akhir. Turn yang dikembalikan membawa finalResponse, daftar lengkap item, dan pemakaian token, jadi Anda mendapat jawaban sekaligus jejak audit dari satu panggilan.
npm install @openai/codex-sdk # pulls the matching @openai/codex CLI as a dependency
// first-thread.ts — Node 18+, server-side only
import { Codex } from "@openai/codex-sdk";
// The SDK spawns `codex exec --experimental-json` and reads JSONL from stdout.
// apiKey is forwarded to the CLI as CODEX_API_KEY.
const codex = new Codex({ apiKey: process.env.CODEX_API_KEY });
const thread = codex.startThread({
workingDirectory: process.cwd(), // must be a Git repo unless skipGitRepoCheck: true
sandboxMode: "read-only",
approvalPolicy: "never", // a script cannot answer an approval prompt
});
const turn = await thread.run("Explain how the test suite in this repo is organised.");
console.log(turn.finalResponse); // the agent's last message
console.log(turn.items.length); // every command, file change and message in the turn
console.log(turn.usage); // input, cached, output and reasoning tokens
// thread.id is null until the first turn has started — read it after run().
console.log("resume later with", thread.id);
Dua detail di sini membuang waktu kalau dilewatkan. Working directory harus berupa repository Git kecuali Anda mengirim skipGitRepoCheck, sebuah pengaman yang disengaja agar edit dari agent selalu bisa dipulihkan dengan git; di CI hasil checkout sudah memenuhinya. Selain itu thread.id tetap null sampai turn pertama dimulai, jadi kode yang menyimpan id sebelum memanggil run() sebenarnya tidak menyimpan apa-apa.
Sandbox mode dan approval policy untuk run tanpa pengawasan
Sandbox menentukan apa yang boleh disentuh perintah agent; approval policy menentukan kapan ia berhenti untuk bertanya kepada manusia. Di terminal, default-nya disetel untuk orang yang sedang mengawasi. Di CI tidak ada yang mengawasi, jadi set keduanya secara eksplisit di setiap thread alih-alih mewarisi apa pun yang tertulis di konfigurasi Codex milik runner.
sandboxMode TypeScript
Preset Python
Di mana saya memakainya
read-only
Sandbox.read_only
Triage, review, dan penjelasan. Agent bisa membaca dan menjalankan perintah read-only tetapi tidak bisa mengubah checkout.
workspace-write
Sandbox.workspace_write
Menyusun fix atau migrasi. Penulisan tetap di dalam working directory; akses network adalah switch terpisah.
danger-full-access
Sandbox.full_access
Hampir tidak pernah di runner CI, yang menyimpan deploy key dan token. Hanya di dalam container sekali pakai milik Anda sendiri.
Approval policy menerima never, on-request, on-failure, dan untrusted. Script tidak bisa menekan tombol approve, jadi thread tanpa pengawasan memakai never dan mengandalkan sandbox untuk keamanan. Akses network punya flag sendiri, networkAccessEnabled, dan saya mematikannya untuk triage: mendiagnosis kegagalan dari kode dan log seharusnya tidak butuh internet, dan baris log yang berisi prompt injection juga tidak boleh bisa menjangkaunya.
Streaming event dengan runStreamed()
run() menahan semuanya sampai turn selesai. Untuk task CI sepuluh menit, artinya sepuluh menit tanpa output di log job. runStreamed() justru mengembalikan async generator berisi event bertipe, jadi log job menampilkan setiap perintah saat agent menjalankannya.
const { events } = await thread.runStreamed("Fix the failing test in src/invoice.test.ts");
for await (const event of events) {
switch (event.type) {
case "thread.started":
// The earliest moment the id exists. Persist it before anything can crash.
await saveThreadId(event.thread_id);
break;
case "item.completed":
if (event.item.type === "command_execution") {
console.log(`$ ${event.item.command} -> exit ${event.item.exit_code}`);
}
if (event.item.type === "file_change") {
for (const change of event.item.changes) console.log(`${change.kind} ${change.path}`);
}
break;
case "turn.completed":
console.log("tokens in/out", event.usage.input_tokens, event.usage.output_tokens);
break;
case "turn.failed":
// run() throws on this; runStreamed() hands it to you, so you must throw yourself.
throw new Error(event.error.message);
}
}
thread.started membawa thread_id, titik paling awal id itu ada. Simpan di sini jika proses bisa mati di tengah turn.
item.started, item.updated, dan item.completed membungkus item thread: command_execution dengan command, output, dan exit_code, file_change dengan path serta add, update, atau delete, ditambah agent_message, reasoning, todo_list, mcp_tool_call, dan web_search.
turn.completed membawa usage berisi jumlah token input, cached input, output, dan reasoning, yang Anda catat untuk pelacakan biaya per job.
turn.failed dan error menandakan kegagalan. run() mengubah turn.failed menjadi Error yang di-throw untuk Anda; dengan runStreamed() Anda harus melakukannya sendiri.
Poin terakhir itulah yang paling ingin saya garis bawahi. Loop streaming yang hanya menangani item.completed dan turn.completed akan keluar dengan bersih saat turn gagal, dan step CI menjadi hijau tanpa hasil apa pun yang ditulis. Perlakukan turn.failed sebagai exception, setiap kali.
Structured output: buat agent mengembalikan JSON, bukan prosa
Paragraf triage memang enak dibaca tetapi tidak berguna bagi step workflow berikutnya. Mengirim outputSchema pada sebuah turn membuat pesan akhir berupa string JSON yang sesuai schema, sehingga job bisa bercabang berdasarkan field verdict. Schema di bawah memaksa satu dari empat verdict, karena hal paling berguna yang bisa disampaikan triage adalah apakah kode perlu disentuh sama sekali.
// scripts/codex-triage.ts — runs only after the test step has failed
import { readFileSync, writeFileSync } from "node:fs";
import { Codex } from "@openai/codex-sdk";
const TRIAGE_SCHEMA = {
type: "object",
properties: {
verdict: { type: "string", enum: ["product_bug", "test_bug", "flaky", "environment"] },
failingTests: { type: "array", items: { type: "string" } },
rootCause: { type: "string" },
suggestedFix: { type: "string" },
},
required: ["verdict", "failingTests", "rootCause", "suggestedFix"],
additionalProperties: false,
} as const;
const TEN_MINUTES_MS = 10 * 60_000;
const LOG_TAIL_CHARS = 20_000; // the tail holds the stack traces; the head is install noise
const codex = new Codex({ apiKey: process.env.CODEX_API_KEY });
const thread = codex.startThread({
sandboxMode: "read-only", // triage reads; it never writes
approvalPolicy: "never",
networkAccessEnabled: false,
});
const logTail = readFileSync("test-output.log", "utf8").slice(-LOG_TAIL_CHARS);
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), TEN_MINUTES_MS);
try {
const turn = await thread.run(
"The test suite failed. Find the root cause without modifying any file.\n\n" + logTail,
{ outputSchema: TRIAGE_SCHEMA, signal: controller.signal },
);
// With outputSchema set, finalResponse is a JSON string that matches the schema.
const triage = JSON.parse(turn.finalResponse);
writeFileSync(
"triage.json",
JSON.stringify({ threadId: thread.id, usage: turn.usage, ...triage }, null, 2),
);
} finally {
clearTimeout(timer);
}
AbortSignal di TurnOptions adalah batas biayanya. Tanpa itu, turn yang macet berjalan sampai timeout job itu sendiri dan terus menghabiskan token; dengan itu, script yang menentukan anggarannya. Saya juga hanya mengirim bagian akhir log, karena stack trace ada di ujung, sementara output instalasi dependency di awal hanya menambah token tanpa menambah bukti.
Memasang Codex di GitHub Actions untuk men-triage test gagal
Workflow menjalankan test, dan hanya jika gagal ia menjalankan script triage lalu meng-upload triage.json sebagai artifact. Step yang paling penting justru bukan step Codex, melainkan deklarasi shell di step test, karena cara paling jelas untuk menyimpan log diam-diam mematahkan kondisi kegagalannya.
# .github/workflows/test.yml
name: test
on: pull_request
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
# Wrong: the default shell is `bash -e` WITHOUT pipefail, so tee's exit code
# (0) wins and a failing suite turns the step green.
# - run: npm test 2>&1 | tee test-output.log
# Right: an explicit `shell: bash` runs with -eo pipefail.
- run: npm test 2>&1 | tee test-output.log
shell: bash
- name: Triage with Codex
if: failure()
run: npx tsx scripts/codex-triage.ts
env:
CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }}
- uses: actions/upload-artifact@v4
if: failure()
with:
name: codex-triage
path: triage.json
Jebakan pipe: GitHub Actions menjalankan shell yang tidak dideklarasikan sebagai bash -e tanpa pipefail, jadi npm test yang di-pipe ke tee melaporkan exit code dari tee, yaitu nol. Test suite gagal, step lolos, if: failure() tidak pernah terpicu, dan agent tidak pernah berjalan. Mendeklarasikan shell: bash mengubah step menjadi bash dengan -eo pipefail sehingga kegagalannya ikut diteruskan.
Menyusun fix adalah turn kedua di thread yang sama, bukan percakapan baru. resumeThread menerima thread options yang baru, jadi thread triage yang tadinya read-only bisa dilanjutkan dengan workspace-write dan sudah tahu apa yang ia temukan. Session tersimpan di disk runner, jadi ini berjalan di dalam satu job; lintas job Anda perlu membawa direktori sessions Codex.
// scripts/codex-draft-fix.ts — same job, after a human-readable triage exists
import { readFileSync } from "node:fs";
import { Codex } from "@openai/codex-sdk";
const { threadId, verdict } = JSON.parse(readFileSync("triage.json", "utf8"));
if (verdict !== "product_bug" && verdict !== "test_bug") process.exit(0); // never "fix" a flake
const codex = new Codex({ apiKey: process.env.CODEX_API_KEY });
// Same conversation, wider permissions: resumeThread takes fresh ThreadOptions.
const thread = codex.resumeThread(threadId, {
sandboxMode: "workspace-write",
approvalPolicy: "never",
});
await thread.run(
"Apply the fix you proposed. Keep the diff minimal, then run the failing tests again.",
);
// The workflow, not the agent, commits and opens the PR: git switch -c, git commit, gh pr create.
Saring berdasarkan verdict. Hanya product_bug dan test_bug yang lanjut; verdict flaky atau environment mengakhiri job, karena agent yang diminta memperbaiki flake akan dengan senang hati menambahkan retry dan menyembunyikannya.
Lanjutkan thread dengan sandboxMode workspace-write dan minta fix seminimal mungkin, plus menjalankan ulang test yang gagal.
Biarkan workflow, bukan agent, yang membuat branch, melakukan commit, dan membuka pull request dengan gh CLI, agar izin untuk push tetap berada di YAML tempat reviewer bisa melihatnya.
Buka sebagai draft dan beri label bahwa ini ditulis agent. Manusia tetap yang menyetujui merge; agent hanya menghemat satu jam pertama investigasi.
Pola yang sama di Python dengan openai-codex
SDK Python mencakup hal yang sama dengan penamaan ala Python: Codex sebagai context manager, thread_start() alih-alih startThread(), final_response alih-alih finalResponse, dan client AsyncCodex untuk kode asyncio. Perbedaan paling bergunanya adalah sandbox bisa di-override pada satu panggilan run(), tidak hanya saat thread dimulai.
# pip install openai-codex (Python 3.10+)
from openai_codex import Codex, Sandbox
with Codex() as codex:
thread = codex.thread_start(sandbox=Sandbox.workspace_write)
thread.run("Migrate the date helpers in src/utils from moment to date-fns.")
# Same thread, narrower sandbox for this one run: the reviewer cannot edit
# the code it is reviewing.
review = thread.run("Review the diff only. List anything risky.", sandbox=Sandbox.read_only)
print(review.final_response)
Override per run itu menghasilkan loop tulis-lalu-review yang rapi: thread yang sama yang membuat perubahan me-review diff-nya sendiri di bawah read_only, sehingga tahap review secara fisik tidak bisa mengedit hal yang sedang ia nilai. Di TypeScript padanannya adalah melanjutkan thread dengan options berbeda, seperti di contoh CI. README Python juga mendokumentasikan helper login eksplisit, termasuk login dengan API key dan device code, yang penting di server headless.
Apa yang diubah DevDay 2026 untuk Codex, dan apa yang tidak
Di DevDay pada 29 September 2026, OpenAI memberi Codex cloud environment yang bisa dipakai ulang dan dibagikan lintas perangkat dengan pengaturan dan izin yang sudah disetujui, CLI dengan kontrol suara dan tampilan /agents baru untuk mendelegasikan serta memantau beberapa task, perbaikan pada session resume dan worktree, serta pengalaman code review baru di aplikasi desktop ChatGPT yang bisa melakukan review otomatis saat Anda sedang offline.
Tidak satu pun dari itu menggantikan SDK untuk pekerjaan pipeline. SDK tetap menggerakkan agent lokal di direktori yang Anda kendalikan, dan itulah persisnya sebuah runner CI. Cloud environment dan code review yang di-hosting lebih cocok ketika pekerjaan itu sama sekali tidak boleh berjalan di infrastruktur Anda, dan tampilan /agents ditujukan bagi orang yang sedang mengatur banyak task di terminal. Pilih berdasarkan di mana kode seharusnya dieksekusi, bukan berdasarkan fitur mana yang paling baru.
Mulai setiap otomatisasi baru dalam mode read-only dengan outputSchema, dan biarkan begitu selama seminggu. Membaca verdict-nya memberi tahu Anda apakah agent memahami repository Anda sebelum ia diizinkan menulis satu baris pun, dan minggu itu tidak memerlukan rollback apa pun.
Aturan yang saya bawa dari sini: agent tanpa pengawasan adalah step CI seperti yang lain, jadi beri disiplin yang sama. Set sandbox dan approval policy di setiap thread, minta JSON alih-alih prosa, beri batas pada setiap turn dengan AbortSignal, perlakukan turn.failed sebagai exception, dan biarkan commit serta push tetap di workflow tempat reviewer bisa melihatnya. Mulai dari read-only, lalu dapatkan akses tulis secara bertahap.