You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 01f4b67
Browse filesBrowse the repository at this point in the historyBrowse files
feat(check): add the opt-in Vapi checks PR workflow and user docs
.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>
5. Run against the deployed resources with `npm run sim`, or against the local files (nothing deployed) with `npm run check` — see "PR Checks" in the README. The PR workflow runs `npm run check` on every affected PR when `VAPI_CHECKS_ENABLED=true`
`tmp/check-payloads/`shows exactly what would be sent. The build fails,
521
+
naming the field, when it can't make a safe, faithful payload:
522
+
523
+
| Problem | Fix |
524
+
| --- | --- |
525
+
| A tool that can't be mocked (SMS, MCP, code, integrations), or an `apiRequest` with no `name` | Remove it, name the `apiRequest`, or set `toolMocks: off` with a dedicated CI org |
526
+
| A tool referenced by ID, or a `toolRefs` pin, with no file in `resources/<org>/tools/` | Pull the tool into gitops |
527
+
| A handoff leaving the squad (`dynamic`, another squad, a non-member), or an assistant target that hands off | Make the target a squad of those assistants |
528
+
| A legacy `assistantDestinations` entry naming an assistant by ID | Convert it to a handoff tool |
529
+
| Tools by ID inside `assistantOverrides`, `membersOverrides` or `targetOverrides` | Put them inline in `tools:append` |
530
+
| Tools outside `model.tools` / `model.toolIds` (`model.functions`, reasoner skills, a recording-consent decline tool) | Move them to `model.tools` |
531
+
| `model.knowledgeBaseId`, a custom knowledge base, or a knowledge base / `query` tool when the check runs in another org | Use a knowledge-base tool in the same org |
532
+
| Personality tools beyond `endCall`-style ones | Keep the personality free of side-effect tools |
533
+
| Two tools with the same type and name on one assistant | Rename one |
534
+
| Audio judges, scenario hooks, or no required text judge over chat | Add a text judge, or use `transport: voice` |
535
+
536
+
Two things to know:
537
+
538
+
- **Transfers never happen.** Every `transferCall` becomes a mocked
539
+
function, so a scenario that needs a real transfer fails rather than
540
+
falsely passing.
541
+
- **Handoff names.** The check warns when a prompt mentions an
542
+
auto-generated `handoff_to_…` name — those differ between inline and
543
+
deployed assistants. Give that handoff an explicit `function.name`.
544
+
545
+
### 4. Live run locally
546
+
547
+
```bash
548
+
npm run check -- core
549
+
```
550
+
551
+
Uses `.env.<org>`, prints the run link, and exits 0 passed, 1 failed, 2
552
+
config or build error, 3 incomplete. It uses simulation minutes.
553
+
554
+
### 5. Turn on the PR workflow
555
+
556
+
In GitHub → Settings → Secrets and variables → Actions:
557
+
558
+
- Secret `VAPI_PRIVATE_API_KEY` (single org), or `VAPI_CHECK_TOKENS` =
559
+
`{"my-org":"<private key>"}`(several orgs, or a CI org).
560
+
- Variable `VAPI_CHECKS_ENABLED=true`.
561
+
562
+
`.github/workflows/vapi-checks.yml`then runs every affected check on each
563
+
PR push. It asks for `statuses: write` only to post the direct links.
564
+
565
+
### 6. What a PR shows
566
+
567
+
- `Vapi Evals`, plus `Vapi Evals / <check> / <target>` per target. **Details**
568
+
opens the run in Vapi.
569
+
- A job summary: per-target result, failing judges with expected vs actual,
570
+
and any "unmocked tool called" notices. No PR comments.
571
+
- PRs that touch neither a check's org, its state, `vapi-checks.yml`,
572
+
`promotion.yml`, the engine (`src/**`, `package*.json`), nor the check's
573
+
own `paths` skip it, and `Vapi Evals` posts success.
574
+
- A newer push cancels the older run.
575
+
576
+
### 7. Make it required (after a burn-in)
577
+
578
+
Require the **commit status `Vapi Evals`** in branch protection — not the
579
+
`vapi-checks`job (fork dry runs succeed) and not the per-target statuses
580
+
(PRs that don't touch a check never get them).
581
+
582
+
- **Fork PRs** run a dry run without secrets and can't post statuses (the
583
+
token is read-only), so a required `Vapi Evals` blocks them.
584
+
- **Dependabot PRs** that change `package*.json` post `error`.
585
+
- **To unblock either**, a maintainer runs Actions → Vapi checks → Run
586
+
workflow on the PR's branch with `check` blank (push a fork's branch into
587
+
the repository first). A later PR event on the same commit resets the
588
+
status, so dispatch again after that. Running one named check by hand
589
+
never changes `Vapi Evals`.
590
+
591
+
### Dedicated CI org (optional; recommended with `toolMocks: off`)
592
+
593
+
1. `npm run setup -- my-ci-org --resources none`.
594
+
2. Create the credentials your agents need there, with the same names as
595
+
production.
596
+
3. `npm run pull -- my-ci-org --bootstrap --bindings-only`, then commit
597
+
`.vapi-state.my-ci-org.json`so the state knows those credentials. The PR
598
+
workflow refreshes bindings on every live run.
599
+
4. Add `runOrg: my-ci-org` (and `baseUrl` for EU) to the check, and
600
+
optionally `bindings:` (same shape as `promotion.yml`). Phone numbers are
601
+
omitted by default.
602
+
5. Put only the CI org's key in `VAPI_CHECK_TOKENS`.
603
+
604
+
### Cost and safety
605
+
606
+
- Every affected push starts paid runs; chat transport is the default.
607
+
- Tool calls get their scenario mock or an error, and every assistant and
608
+
function-tool server points at `https://vapi-gitops-ci.invalid`.
609
+
- Still real in the run org: custom LLM, voice and transcriber servers see
610
+
the conversation; org-wide and assistant monitors run; `observabilityPlan`
611
+
exports transcripts; prompts are stored with the run. A CI org avoids all
612
+
of these.
613
+
- `.ts`resource files execute during the check, with the workflow's secrets
614
+
on same-repository PRs.
615
+
616
+
---
617
+
459
618
## How to Use This Repo
460
619
461
620
1. **Run `npm run setup`** to configure your first org (or `npm run setup -- <org>` without a terminal)
|[voice-providers.md](voice-providers.md)| Per-provider voice block layout (Cartesia vs 11labs vs OpenAI/Azure/Rime/LMNT/Minimax/Neuphonic/SmallestAI) — saves 400s at push time |
0 commit comments