DEV Community

Cover image for Bagaimana osFoundry hanya push satu branch saat Codex CLI meminta pull request GitHub?
BBT for BBT Indonesia

Posted on

Bagaimana osFoundry hanya push satu branch saat Codex CLI meminta pull request GitHub?

Ini tool yang dipanggil Codex CLI saat ingin membawa change set ke GitHub atau GitLab, persis seperti yang ditampilkan server MCP osFoundry ke client mana pun, baik sudah login maupun belum:

{"name": "propose_gitnest_provider_publication",
 "description": "Ask the repository owner to review a composed GitNest change set, push only its dedicated branch using their linked Git provider account, and open a draft pull request. Approval rechecks permissions, risk policy, required checks, and target commit.",
 "inputSchema": {"properties": {
     "repository_id": {"type": "string"},
     "change_set_id": {"type": "string"},
     "expected_change_set_version": {"type": "integer", "minimum": 1},
     "expected_composition_oid": {"type": "string"},
     "idempotency_key": {"type": "string", "minLength": 8, "maxLength": 128}},
   "required": ["repository_id", "change_set_id", "expected_change_set_version",
                "expected_composition_oid", "idempotency_key"],
   "additionalProperties": false},
 "annotations": {"readOnlyHint": false, "openWorldHint": true, "destructiveHint": true},
 "securitySchemes": [{"type": "oauth2", "scopes": ["osfoundry:write"]}]}
Enter fullscreen mode Exit fullscreen mode

Perhatikan apa yang tidak ada. Tidak ada nama branch, judul, branch tujuan, flag draft, ataupun token. Client hanya menyebut change set dan dua fakta yang ia amati tentang change set itu, dan semua sisanya datang dari pihak owner saat owner memberi persetujuan. "Push only its dedicated branch" adalah janji yang dibedah di post ini. Post ini juga membahas tool yang biasanya dipanggil lebih dulu, propose_gitnest_file_import, dan apa arti budget 200 KB-nya untuk file sungguhan.

Di Mac ini tidak ada binary codex yang terpasang, jadi sisi client-nya berupa script Python yang dijalankan di sini (Apple M1, Python 3.12 lewat uv, git 2.39.5). Angka jaringan diukur dari Jepang.

$ uv run --with tiktoken --with jsonschema python3 publish_sim.py
== Part 1: live tools/list (no token) ==
tools/list x5 median 505.7 ms; 59 tools, 13 GitNest
read_gitnest_repository               osfoundry:read  ro=True  openWorld=False destructive=False
propose_gitnest_file_import           osfoundry:write ro=False openWorld=False destructive=True
propose_gitnest_native_publication    osfoundry:write ro=False openWorld=True  destructive=True
propose_gitnest_provider_publication  osfoundry:write ro=False openWorld=True  destructive=True
these four = 989 of 12662 tokens
Enter fullscreen mode Exit fullscreen mode

(Daftar field wajib dipotong dari output di atas. Token dihitung dengan cl100k_base, jadi hanya perkiraan untuk tokenizer milik Codex sendiri.) Keempat tool ini memakan kurang dari 1,000 token dari total 12,662 token di daftar. Config Codex yang menyebut keempatnya di enabled_tools membuat 55 tool lainnya tidak masuk ke prompt.

Kenapa kedua tool publikasi bersifat "open world", sedangkan import tidak?

openWorldHint memberi tahu client apakah sebuah tool menjangkau keluar dari sistem tertutup. File import menulis ke repository GitNest yang hanya bisa dilihat oleh project. Pull request, bahkan yang native, adalah sesuatu yang memicu notifikasi ke orang lain dan ditindaklanjuti oleh mereka. Codex memakai anotasi seperti ini untuk memutuskan kapan harus berhenti dan bertanya, jadi hint dari server adalah lapisan pertama rantai persetujuan, bahkan sebelum kartu milik osFoundry sendiri.

Apa saja yang bisa dibawa oleh file import?

Sebagian besar dijawab oleh schema. Dua belas kasus diuji terhadap inputSchema asli, divalidasi dengan jsonschema (Draft 2020-12):

file_import            20 files                           ok
file_import            21 files                           REJECT maxItems
file_import            one file 100,000 chars             ok
file_import            one file 100,001 chars             REJECT maxLength
file_import            file with mode '100755'            REJECT additionalProperties
file_import            delete: {'path','delete':true}     REJECT required
file_import            idempotency_key 7 chars            REJECT minLength
native_publication     full pin                           ok
native_publication     no expected_composition_oid        REJECT required
provider_publication   expected_change_set_version 0      REJECT minimum
provider_publication   extra 'draft': false               REJECT additionalProperties
provider_publication   extra 'target_branch'              REJECT additionalProperties
Enter fullscreen mode Exit fullscreen mode

Satu entri file hanya berisi path dan content, tidak lebih. Tidak ada mode bit, jadi script akan masuk dalam keadaan tidak executable. Tidak ada delete, karena delete butuh entri tanpa content. Setiap entri menambahkan atau mengganti file dengan teks lengkapnya, dan itu juga persis yang dibaca owner di kartu persetujuan.

Ada satu batas yang tidak bisa dinyatakan schema, yaitu total 200 KB yang disebut di description. maxLength menghitung karakter. Budget-nya menghitung byte UTF-8:

JA file: 100,000 chars passes maxLength; UTF-8 = 300,000 bytes
Enter fullscreen mode Exit fullscreen mode

File berbahasa Jepang yang menyentuh batas karakter per file sudah satu setengah kali budget seluruh commit. Client yang hanya mengecek schema akan membuat call yang terpaksa ditolak server. Hitung byte-nya di sisi client.

Jadi berapa kartu persetujuan untuk import sungguhan?

Bagian 3 script membagi direktori sungguhan ke dalam call import di bawah 20 file dan 200 KB, dengan first-fit berdasarkan ukuran, lalu menghitung jumlah proposal. Tiap proposal adalah satu kartu yang harus dibuka owner:

GitNest web lib (.ts)           25 files   126,756 chars   127,210 bytes (1.00 B/char) | 2 proposals
   per proposal (files, KB): 20/115, 5/12
GitNest web components (.tsx)   22 files   274,206 chars   274,819 bytes (1.00 B/char) | 2 proposals
   per proposal (files, KB): 10/200, 12/75
Team 2 JA articles (.md)        55 files   359,213 chars   662,285 bytes (1.84 B/char) | 4 proposals
   per proposal (files, KB): 13/200, 16/196, 18/194, 8/73
Enter fullscreen mode Exit fullscreen mode

Korpusnya adalah source frontend osFoundry sendiri dan draft berbahasa Jepang dari blog ini, dibaca langsung dari disk. File source kecil lebih dulu mentok di batas 20 file. Komponen besar dan teks non-Latin lebih dulu mentok di budget byte. Mau "KB" dibaca 1,000 atau 1,024 byte, hasil untuk ketiga set ini tidak berubah.

Hal lain yang perlu diperhatikan adalah jumlah commit. Empat proposal berarti empat commit di branch, disetujui satu per satu, dan owner bisa menolak yang mana saja. Kalau file-file itu hanya masuk akal kalau bersama, tulis itu di setiap commit message, atau buat import-nya cukup kecil supaya muat dalam satu kartu.

Apa arti "push only its dedicated branch" di git?

Push ke provider yang dilakukan osFoundry di sisi server tidak publik. Push milik aplikasi desktop, yang dipakai tombol Push Change Set Branch di GitNest, bisa dilihat: gitnest_push_change_set_branch di gitnest_desktop.rs. Fungsi ini menerima nilai yang dikembalikan preflight publikasi lalu melakukan ini, berurutan:

  1. Membaca fetch URL dan push URL milik origin, lalu menolak kalau keduanya mengarah ke repository berbeda, atau tidak mengarah ke provider, owner, dan repo yang disebut preflight.
  2. Mewajibkan nama branch persis osfoundry/change-set-<id>, dengan <id> berupa UUID.
  3. Mewajibkan marker persis GitNest-Change-Set: <id>.
  4. Mewajibkan composition OID berupa 4 sampai 64 karakter hex, lalu menjalankan git cat-file -e <oid>^{commit}.
  5. Menjalankan git update-ref refs/heads/<branch> <oid> <old>, dengan <old> adalah isi ref saat dibaca, atau kosong kalau ref itu belum ada.
  6. Push satu refspec saja: git push origin refs/heads/<branch>:refs/heads/<branch>.

Script ini membangun ulang alur itu terhadap bare repository yang berperan sebagai GitHub. Clone-nya punya dua hal yang belum di-push owner: branch lokal wip/secret-pricing dan satu commit tambahan di release/2.4. Commit hasil komposisi dibuat tanpa worktree (index sekali pakai, hash-object, update-index --cacheinfo, write-tree, commit-tree):

remote refs before: 3  (dana/refactor-cart, main, release/2.4)
`git push --all` would change 2 remote refs: ['release/2.4', 'wip/secret-pricing']
explicit refspec push changed 1 ref: osfoundry/change-set-5b0c2e8e-3f6a-4d2b-9a71-0c4e9d1f7a23 -> 73e59f27bc4f
untouched: True; marker in pushed commit: True
Enter fullscreen mode Exit fullscreen mode

Baris kedua itulah alasan utama refspec eksplisit. Push praktis dari clone owner akan ikut memublikasikan hotfix yang belum di-review dan branch pribadi bersama change set-nya. Refspec eksplisit hanya memindahkan satu ref, yang namanya diturunkan dari ID change set, jadi tidak mungkin bentrok dengan branch milik siapa pun.

Apa saja yang ditolak?

branch name 'codex/ci' instead of contract     refused: publication branch does not match the preflight contract
marker for another change set                  refused: publication marker does not match the preflight contract
OID 'HEAD' instead of hex                      refused: composition OID must be a hex commit id
hex OID that is not in the clone               refused: fatal: Not a valid object name deadbeefdeadbeefdeadbeefdeadbeefdeadbeef^{commit}
update-ref with a stale expected old value     exit 128: fatal: update_ref failed for ref 'refs/heads/osfoundry/change-set-5b0c2e8e-3f6a-
push URL differs from fetch URL                refused: origin fetch and push remotes point at different repositories
Enter fullscreen mode Exit fullscreen mode

Kasus "HEAD" terlihat terlalu rewel, padahal tidak. Nama simbolik akan di-resolve ke apa pun yang sedang di-checkout saat push, dan itu belum tentu yang dikomposisi dan di-review. OID hex hanya bisa berarti satu commit. update-ref dengan nilai lama yang sudah basi adalah compare-and-swap bawaan git: kalau branch lokal bergeser di antara baca dan tulis, penulisan gagal alih-alih menimpanya diam-diam. Cek URL terpisah menutup celah yang lebih halus: git remote set-url --push bisa mengarahkan push ke tempat lain dari asal fetch, dan kodenya mewajibkan keduanya menunjuk repository yang sama.

Apa yang ditambahkan oleh pengecekan ulang saat persetujuan?

Langkah-langkah git melindungi push. Dua field expected_ melindungi keputusannya. Bagian 5 script merekonstruksi pengecekan ulang dari description tool ("Approval rechecks permissions, risk policy, required checks, and target commit"):

as proposed                          publish
recomposed to v4 before the click    refused: change set is v4, card says v3
lint re-ran and failed               refused: required checks not passed: ['lint']
Enter fullscreen mode Exit fullscreen mode

Codex membaca versi 3 dan commit 73e59f27bc4f…, dan kartu owner menampilkan keduanya. Kalau di sela waktu itu ada yang mengomposisi ulang change set, menyetujui kartu tersebut berarti memublikasikan sesuatu yang belum dilihat siapa pun, jadi proposal-nya gagal. Versi saja tidak cukup, karena dua komposisi dengan versi yang sama bisa berbeda. OID saja juga tidak cukup, karena OID tidak memberi tahu edit change set mana yang dimaksud owner. Mengunci keduanya hanya menambah dua field di client.

Berapa biayanya?

20 runs, local bare remote: compose median 114.2 ms, checks+update-ref+push median 140.0 ms (max 430.0)
Enter fullscreen mode Exit fullscreen mode

Enam pengecekan dan push menambah sekitar 140 ms terhadap bare remote lokal. Dengan provider sungguhan, round trip jaringan yang paling dominan. Seluruh script, termasuk 5 call tools/list sungguhan dan 21 publikasi, memakan 11.7 s wall-clock dan 3.5 s user CPU.

Kalau mau merakitnya sendiri

  • Biarkan agent menyebut change set dan apa yang ia amati (versi dan commit), jangan pernah branch, judul, target, atau token. Turunkan nama branch dari ID change set.
  • Push dengan satu refspec eksplisit. Jangan pernah --all, --mirror, atau git push polos dari clone yang juga dipakai manusia.
  • Tulis ref lokal dengan nilai lama yang diharapkan, dan wajibkan fetch URL dan push URL menunjuk repository yang sama.
  • Hanya terima OID hex untuk apa pun yang akan dipublikasikan. Resolve nama simbolik sebelum review, bukan sesudahnya.
  • Nyatakan budget import dalam byte dan cek di sisi client. Batas karakter di schema tidak akan menangkap teks non-Latin.
  • Tandai tool yang menjangkau orang lain dengan openWorldHint: true, supaya prompt persetujuan milik client sendiri muncul sebelum milik Anda.

Bagaimana change set, check, dan publikasi saling terhubung di dalam repository dijelaskan di halaman GitNest, sedangkan sisi protokolnya ada di entri glosarium MCP.

Top comments (0)