Skip to content

Why API-first (not local-clone-write)

When PETROVA’s verbs need to write to a consumer repo, they do so via the GitHub Contents API, not by editing a local clone and pushing. This page explains why.

What the rejected approach would have looked like

Section titled “What the rejected approach would have looked like”

A reasonable alternative: the verb has access to a local clone of the target repo, edits files directly, runs git add && git commit && git push, then opens a PR via gh pr create. Many automation tools work this way.

PETROVA explicitly does not.

1. Two clones drift; one canonical write path doesn’t

Section titled “1. Two clones drift; one canonical write path doesn’t”

If verbs write through local clones, you have two sources of truth on disk: the operator’s working tree, and any agent fleet’s working tree. They’ll diverge — the operator pulls, the fleet doesn’t (or vice versa), and a verb retry against the fleet’s stale clone overwrites work the operator just landed.

API writes have no working tree. Every commit is built from the target branch’s current SHA, freshly fetched. Concurrent edits surface as 422 Unprocessable Entity (SHA mismatch) rather than silent overwrites.

A local-clone-write design needs git push credentials wherever the verb runs. A fleet running in a CI job, a local session, and an agent service would each need credentials provisioned, rotated, and revoked separately.

API writes go through one auth surface — the PETROVA_GITHUB_TOKEN PAT or GitHub App installation. One identity, one rotation cycle, one revocation switch.

3. The audit trail lives in PR bodies, not commits

Section titled “3. The audit trail lives in PR bodies, not commits”

Every PETROVA PR body carries a metadata block with the verb, idempotency key, actor, trigger, schema fingerprint, and MR citations. None of that fits in a commit message.

Local-clone writes make the commit the audit unit. API writes make the PR the audit unit, with commits as a thin substrate. This matches PETROVA’s verb-shaped semantics — a verb is one PR, with N commits inside.

4. No working tree means no working-tree state to manage

Section titled “4. No working tree means no working-tree state to manage”

Local-clone writes have to think about: shallow clones (faster but limit git log reach), branch protection that requires signed commits (would need GPG keys provisioned), .gitattributes smudge filters (would run unintended transforms), pre-commit hooks (would fire and possibly fail). API writes sidestep all of this — they produce blobs and trees directly via the API.

  • Multi-file commits are awkward. The Contents API commits one file per request. For a verb that needs to land 5 files atomically, we make 5 sequential calls. Tolerable at PETROVA’s scale (most verbs touch 1–2 files); the documented upgrade path is the Trees API for atomic multi-file commits when this becomes a problem.
  • Rate limits are coarser. A PAT gets ~5000 req/hr; a GitHub App installation ~15000. With one verb = ~6 API calls (get default branch SHA, get file SHAs, put files, create PR, optionally enable auto-merge), you get ~830 verb invocations/hour on PAT and ~2500 on App. Plenty for current scale.
  • Latency is meaningful. Each verb round-trip is ~10–30 seconds vs ~1 second for local-clone-and-push. We accepted this trade for the audit + concentration benefits above.