Repository navigation
feat(check): add the opt-in Vapi checks PR workflow and user docs - #64
Merged
Merged
Conversation
This was referenced Oct 1, 2026
Contributor
Author
scott-lowe-vapi
marked this pull request as ready for review
October 1, 2026 23:52
vtkovapi
approved these changes
Oct 3, 2026
Contributor
Author
Merge activity
|
scott-lowe-vapi
changed the base branch from
feat/check-live-run
to
graphite-base/64
October 3, 2026 06:10
.github/workflows/vapi-checks.yml runs `npm run check` on pull requests (opened, synchronize, reopened, ready_for_review) and on manual dispatch, only when the repository variable VAPI_CHECKS_ENABLED is 'true' and a vapi-checks.yml exists — so on the upstream template it is dormant. - Live runs only for same-repository, non-Dependabot PRs and dispatch; forks and Dependabot get a keyless dry run, and secrets are passed only to live runs. Never pull_request_target. - Checks out the head SHA with full history (for --changed-since against origin/<base>) and persist-credentials: false. - --all posts the aggregate `Vapi Evals` status; dispatching one named check doesn't. The run step execs node so GitHub's cancel reaches it, inside a 22-minute budget under a 30-minute job timeout; concurrency cancels a superseded push's runs. - permissions: contents: read, statuses: write. Docs: a README "PR Checks" section (setup from test files to required status, the build-failure table, fork/Dependabot handling, CI orgs, cost and what stays real), the AGENTS.md simulations step, a "Inline PR Checks" section in docs/learnings/simulations.md, and improvements.md #34. Refs TEST-141 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scott-lowe-vapi
force-pushed
the
feat/vapi-checks-workflow
branch
from
October 3, 2026 06:12
8319eb0 to
01f4b67
Compare
scott-lowe-vapi
added a commit
that referenced
this pull request
Oct 7, 2026
## Value **V.A.L.U.E. tier:** project — PR 10 of 10 for inline simulation PR checks ([TEST-141](https://linear.app/vapi/issue/TEST-141/gitops-run-simulation-suites-against-pr-changes-inline-as-ci-checks)), the "check before deploy" step in promotion. > Stacked on #65 (the promotion partial-failure fix), now that #57–#64 have merged. This PR is the single gate commit. - **Problem:** promotion copies staging's reviewed files into production, but nothing checks that staging's agents still behave before they move on. Teams promoting dev → staging → prod need a behaviour gate between orgs, without new infrastructure. - **Who it affects:** multi-org gitops users (the promotion pipeline), who get a "check before deploy" step with one line of `promotion.yml`. Single-org users are unaffected. - **What changes:** - **`promotion.yml`** accepts `orgs.<slug>.check: <name>` (a slug), naming a `vapi-checks.yml` check. - **New `src/promotion-gate.ts`:** - **Validation before any transition:** the check must exist, and its `org` and `runOrg` must be the gated org, otherwise the run errors. `vapi-checks.yml` is required once any org is gated. - **Plan line:** what the gate would run, built offline. - **Live gate:** the check runs live and reduces to the worst target result. - **`src/promote-cmd.ts`:** in each transition, after the plan is built: - no changes skips the gate; - plan-only prints `check would run <name> in <org> (<n> simulations × <t> targets)`; - `--apply` runs the check (after the bindings refresh, before `promotionPlanApply` writes anything). Any non-pass throws `Promotion out of <org> blocked: check <name> <outcome> (<run url>)`. - A pass is cached per source org and dropped once a transition applies into that org. - `promotionCommandRun(args, overrides)` now takes `Partial<PromotionDeps>` (`childRun`, `checkRun`). - **`.github/workflows/promotion.yml`:** `timeout-minutes: 90` on the "Reconcile configured promotions" **step**, not the job, so the `if: always()` commit step (fixed in #65) still runs after a blocked or slow gate. - **Docs:** `promotion.example.yml` (a commented `check:`), a README "Check before promoting" section, and a pointer from "PR Checks". ## Evidence of value **The real gate, run live** in the owner's test org on the TEST-141 parity squad. - **Setup:** a scratch repo whose `promotion.yml` gates `parity` on check `core`, with pipeline `parity → parity-prod`. - **The run:** `promote --pipeline release --from parity --to parity-prod --apply`. - **The fake:** the child runner was faked, so bindings pulls were no-ops and the downstream `apply.ts` was recorded but not run. No second org was needed or touched. | Variant | Gate run | Result | Downstream apply | `resources/parity-prod/` | |---|---|---|---|---| | Degraded scheduler prompt | [7ed19587](https://dashboard.vapi.ai/simulations/run/7ed19587-d1ca-4d44-9232-7cdd15a50d67): 2 of 3 failed | `Promotion out of parity blocked: check core failed (https://dashboard.vapi.ai/simulations/run/7ed19587-…)` | **none** | **empty** (nothing written) | | Fixture as-is | [95470670](https://dashboard.vapi.ai/simulations/run/95470670-2861-45d3-a483-7a220fc3591a): 3 of 3 passed | promoted | `["parity-prod"]` | written; 20 applied paths recorded | The test org's resource counts were identical before and after both gate runs. **Tests:** `npm test` goes from 484 (#65) to 492 passing, and #68's golden promotion test passes unchanged. ## Testing plan - **`tests/promotion-gate.test.ts`** (6 tests, real git fixture, injected `childRun` / `checkRun`): - a pass applies; - failed and incomplete both block with the exact message, with no apply and the target untouched; - plan-only prints the line and runs nothing; - no changes skips the gate; - the three config errors (no `vapi-checks.yml`, unknown check, check in another org) stop before anything applies; - the pass cache: reused for two pipelines out of one org, and re-run after a transition applies into the gated org. - **No gate configured, no change:** with no `check:` in `promotion.yml` and an **invalid** `vapi-checks.yml` present, plan and `--apply` both succeed, `checkRun` is never called, and the plan output equals a pinned string. That string is exactly what #65's code (before the gate existed) prints for the same fixture, which I confirmed by running #65's `promote-cmd` on it. So the gate is invisible unless someone opts in. - **`tests/promotion.test.ts`:** `orgs.<slug>.check` is parsed, and a non-slug is rejected. - **Not tested:** - **A real two-org promotion:** only one test org was available. The downstream apply was faked, so the blocked case shows nothing written, and the pass case shows the apply was called. - **A GitHub Actions promotion run with a gate**, including the step timeout firing. - **Found while testing (pre-existing, out of scope):** promotion's dependency check rejects simulations that reference a **stock personality by UUID**, with "Referenced managed dependency is missing from source: personalities/a0000000-…". So a gated org's tests need local personality files until that's fixed. Stacked on #65. Refs TEST-141 ## After review The gate now refuses, when the config loads and before anything applies: - an unknown key under an org in `promotion.yml`, so a misspelled `check:` can't silently drop the gate; - `toolMocks: off` and `stripWebhooks: false`, because a gate runs in the real org, never a CI org; - a check `baseUrl` that differs from the org's `baseUrl` in `promotion.yml`, so the org's key only goes to the host promotion uses (the gate always uses that host); - a gate on an org that is last in every pipeline, where it would never run; - gated checks whose combined budget is over 300 minutes. It also fixes: - **Deadline:** each batch of 3 targets gets a full `timeoutMinutes`, so a check with more than 3 targets is no longer falsely blocked as incomplete. - **Step timeout:** raised from 90 to 330 minutes, as a safety net that no longer cuts short long ungated promotions. - **Tests:** the deadline and the worst-target rule are pure helpers with their own tests. The guide changes (blocks stop the whole run, simulation cost, the stock-personality limitation, accurate wording) are in #71. The `promote` User-Agent for gate runs is in #78. The block-report detail, fetch-stubbed gate test and deduplication are follow-ups. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Value
V.A.L.U.E. tier: project — PR 8 of 10 for inline simulation PR checks (TEST-141). This PR turns
npm run checkinto a PR check and documents setup end to end.npm run check(PR 7) gives a verdict locally, but a reviewer needs it on the PR: aVapi Evalsstatus whose Details link opens the exact run (PAL-608's contract), run on every affected push, safe for forks and Dependabot..github/workflows/vapi-checks.yml(opt-in):pull_request(opened, synchronize, reopened, ready_for_review), plusworkflow_dispatchwith an optionalcheckinput. Neverpull_request_target.vars.VAPI_CHECKS_ENABLED == 'true', and does nothing without avapi-checks.yml, so on the upstream template it stays dormant.fetch-depth: 0so--changed-since origin/<base>diffs from the merge base, andpersist-credentials: false.--allposts the aggregateVapi Evals; dispatching one named check never changes it.cancel-in-progress, and the run stepexecs node, so GitHub's cancel reaches it and it cancels the superseded runs.--budget-minutes 22undertimeout-minutes: 30.contents: readandstatuses: write.README "PR Checks": the end-user guide, covering:
Vapi Evalsrequired, and fork/Dependabot unblocking;The project tree gains
vapi-checks.example.ymlandcheck-cmd.ts.AGENTS.md: the "Testing with Simulations" step 5 now points at
npm run simvsnpm run check.docs/learnings/simulations.md"Inline PR Checks":Indexes: the learnings index row, and
improvements.mdrefactor(push): extract reconcileStateKeyForResource — fold two ensure-fns into one generic helper #34 (RESOLVED). Nodocs/changelog.mdedit.Evidence of value
VapiAI/gitopshas noVAPI_CHECKS_ENABLEDvariable, so thevapi-checksjob is skipped in this PR's own checks (run 36941407785:completed / skipped). That also shows GitHub parsed the workflow and evaluated itsif:. Customers who haven't opted in see no change and no spend.--all,--changed-since, live and dry run, statuses, job summary, JSON) is covered end to end by PR 7's tests against a stub of the simulations and GitHub APIs. It was also run live in the owner's test org: innocuous exit 0, degraded exit 1, no resources created, every tool result a mock.npm test: 474 passing (docs and workflow only; no code change).Testing plan
actionlintisn't available in this environment, so it isn't linted. To check by hand:if:expressions, thesecretsternaries in stepenv, and theexecline.resources/<test-org>/holding the parity squad fromtests/fixtures/check-parity/resources/parity/, renamed, and that fixture'svapi-checks.yml.VAPI_PRIVATE_API_KEYsecret andVAPI_CHECKS_ENABLED=true.Vapi Evalsstatuses whose Details links land on the runs, plus the job summaries (screenshots to add here).package.jsonchange as Dependabot would. ExpectVapi Evals=error.itemCounts.canceled > 0).Stacked on #63.
Refs TEST-141
🤖 Generated with Claude Code