Skip to main content

Prefer the CLI: an Agent Configures, It Does Not Instruct

In one line: where a vendor ships a CLI, the agent runs it — a console click-path in a runbook is an instruction to a human, and an instruction to a human is not a mechanism.

A console click-path has three properties that make it the wrong artifact for an agent-driven process. It cannot be verified — nothing reads a UI to confirm the step happened. It cannot be reviewed — there is no diff. And it rots silently, because vendors move menus without telling anyone, so a runbook saying Settings → Rules → New ruleset is correct until it is quietly not. A CLI invocation is none of those things: it is text, so it diffs; it exits non-zero, so it is checkable; and when it breaks it breaks loudly.

This is the §7 gate-admission rule applied to operations. "The developer will click through the console" fails the same test as "the agent will remember".

The rule: if a vendor ships a CLI, the agent uses it. Document the console path only as an orientation aid for a human who wants to see the state — never as the mechanism.

Two worked examples, both from this canon's own repositories.

Cloudflare Pages, via wrangler. The trap here is that deploying is not publishing: a deploy without --branch matching the project's production branch creates a preview at a hash URL and leaves the live site untouched, which looks like success and is not.

# what the production branch actually is — read it, do not assume it
gh api "accounts/$ACCOUNT/pages/projects/$PROJECT" -q .production_branch

npm run build
npx wrangler pages deploy build --project-name=<project> --branch=<production-branch> --commit-dirty=true

# then verify the PRODUCTION host, not the deployment hash the CLI printed
curl -sL -o /dev/null -w '%{http_code}\n' "https://<project>.pages.dev/<a-page-the-change-touched>"

GitHub repository configuration, via gh. A ruleset is created and inspected as JSON, so it can be diffed, reviewed and re-created — none of which is true of the settings screen that produces the same object.

gh api -X POST repos/<owner>/<repo>/rulesets --input ruleset.json
gh api repos/<owner>/<repo>/rulesets -q '.[] | "\(.name) [\(.enforcement)]"'

The reason this matters more for an agent than for a person is that a human reading Settings → Rules will muddle through a renamed menu. An agent handed the same instruction cannot act at all — so the step silently does not happen, and the control it was supposed to create exists only in the runbook that describes it. §7.6 exists because that had already happened: three Core-mechanized rules whose mechanism was a repository setting nobody had been told to create.

Evidence: both examples are commands this canon's own maintenance runs. The Cloudflare --branch trap and the GitHub required-check-name trap (§7.6 step 2) were each found by running the command and checking the result, not by reading the vendor's documentation.