Repository navigation
feat: identify every gitops API request with a User-Agent - #78
scott-lowe-vapi wants to merge 1 commit into
Conversation
chris-garber-vapi
left a comment
There was a problem hiding this comment.
Aggressive review: 1 🟠, 3 🟡, 4 🟢, no blockers.
The main problem: the label doesn't reach child processes when a command isn't started through npm run, so PR check runs are counted as CI pulls (🟠 on user-agent.ts:61). The fix for that also keeps the label set bounded (🟡 on :63).
Every comment says what I checked. Every fix was tried on a copy of 674d8a1: tsc and npm test pass.
Not part of this diff, so no inline comment: under npm run promote, the promotion gate's simulation runs still send vapi-gitops-check/… (promotion-gate.ts:90). That means check-run counts include promotion gates.
Only `npm run sim` and `npm run check` identified themselves. Setup, pull, push, apply, promote, cleanup, rollback, call and audit sent Node's default `node` User-Agent, about a sixth of all api.vapi.ai traffic, so gitops usage beyond simulations couldn't be counted. - src/user-agent.ts: `vapi-gitops-<command>/<version>`, plus ` (ci)` when GITHUB_ACTIONS=true or CI is set (not false or 0). The command comes from a fixed list of this repo's commands, so a fork's own npm script names are never sent and npx is not a label; otherwise the entry script names it, else `cli`. The first process pins its label in VAPI_GITOPS_COMMAND, which spawned processes inherit, so the PR check's bindings pull is labelled check and `npm run apply`'s pull and push are labelled apply. sim and check keep their labels; promotion gate runs are labelled promote, apart from PR check runs. - Every fetch sends it, the GitHub status call included. tests/user-agent-coverage.test.ts checks each call on its own, scans src/ recursively, and checks api.ts against a local server. - cleanup-safety and new-file-gate tests sent about a dozen requests to the real api.vapi.ai per `npm test` (fake key, 401s), which the new User-Agent made visible in the request logs. tests/no-vapi-api.ts now loads before every test file: an unroutable base URL and no inherited real key, for the tests and every CLI they spawn. - how-it-works.md says exactly what the API sees; AGENTS.md says every request sends the header. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1f982af to
908d125
Compare
674d8a1 to
5d90e98
Compare
|
@chris-garber-vapi on the gate label from your summary: done. Promotion gate runs now send 🤖 Generated with Claude Code |

Value
V.A.L.U.E. tier: small — a behavior change: every API request now carries a new header. No blast-radius path.
npm run simandnpm run checkidentify themselves. Setup, pull, push, apply, promote, cleanup, rollback, call and audit send Node's default User-Agent,node, which was 126k of 728kapi.vapi.airequests in one hour this morning. So "how many deploys come from gitops, from CI or from laptops" has no answer.src/user-agent.tsbuildsvapi-gitops-<command>/<version>, plus(ci)whenCIorGITHUB_ACTIONSis set:<command>comes from a fixed list of this repo's commands, so a fork's own npm script names are never sent. The first process pins its label inVAPI_GITOPS_COMMAND, which child processes inherit:npm run applylabels its pull and pushapply, a promotion's applies arepromote, and the PR check's bindings pull ischeck. Promotion gate runs are labelledpromote, apart from PR check runs.simandcheckkeep their fixed labels, which existing simulation analytics already counts by.fetchto the Vapi API sends it:api.ts(push, pull, apply, promote), cleanup, setup, the interactive pickers, rollback, call, and push's direct fetch.cleanup-safetyandnew-file-gatesent about 12 requests pernpm testtoapi.vapi.ai, with a fake key, getting 401s. That breaks the repo's own rule that tests never call the real API, and with this PR it would have counted every fork's CI run as gitops usage. They now point at a dead local address.how-it-works.mdsays exactly what the API sees (and that there is no other telemetry).AGENTS.mdsays every request must send the header.Evidence of value
Live, through the Cloudflare request logs in Axiom (
cloudflare-logpush): a read-onlynpm run setup -- ua-check --resources noneagainst the test org, run from a scratch copy:vapi-gitops-setup/1.0.0vapi-gitops-test/1.0.0npm testruns before the fixBefore this PR, both rows would have been indistinguishable
nodetraffic.Tests:
tests/user-agent-coverage.test.tsreads everyfetch(insrc/and requires aUser-Agent. On the parent branch it lists 10 call sites without one; here it lists none. It also runsapi.tsagainst a local server withnpm_lifecycle_event=apply.tests/user-agent.test.tspins the format: fixed sim and check labels, npm script vs. entry script vs.cli, label cleaning, and the CI marker forGITHUB_ACTIONS=true,CI=trueandCI=1(but notfalse,0or empty).fetchtrap that records any request tovapi.aicaught 12 requests before the test fix and none after.Testing plan
npm test(527 tests) andnpx tsc --noEmitpass.1.0.0; bumping it is deliberately left out of this PR.callcommand's WebSocket audio connection isn't a Vapi REST request and doesn't carry the header.Refs TEST-141
After review
npxand fork script names fall through to the entry script, then tocli.fetchinsrc/does. The coverage test checks each call on its own and scans subfolders.tests/no-vapi-api.ts: loads before every test file, with an unroutable base URL and no inherited real key. With a pretend key exported and a fetch trap on, the suite makes zero requests to vapi.ai.how-it-works.mdmatches the code on(ci)and<command>.🤖 Generated with Claude Code