Repository navigation
feat(validate): catch broken references and show findings on the PR - #77
Merged
Merged
Conversation
This was referenced Oct 3, 2026
Merged
scott-lowe-vapi
marked this pull request as ready for review
October 3, 2026 07:44
Contributor
Author
chris-garber-vapi
left a comment
There was a problem hiding this comment.
Aggressive review pass. Each finding was checked against the code, and most were reproduced by running this branch's validators on the input described in the comment.
Severity: 🔴 blocker · 🟠 fix before merge · 🟡 should fix (a stacked PR is fine where noted) · 🟢 nit
- 🟠 ×3:
validatechecks.vapi-ignored files that push skips, which blocks CI andapply. Theoverride-tool-by-namefix advice (model.tools) replaces the member's tool set (message and docs row). - 🟡 ×6:
- Judge references to ignored structured outputs pass both rules.
- A non-string
toolIdsentry crashesvalidate. - Override,
toolRefsand inline-assistant references aren't checked. reference-by-uuidfires on every PR for dashboard-owned resources.- Annotations have no
line. - The troubleshooting row tells readers to un-ignore resources.
- 🟢 ×3: prototype-key lookup, duplicated credential walk, #31 status wording.
No 🔴. Tests and tsc pass on the branch as-is.
scott-lowe-vapi
force-pushed
the
ci/validate-resources
branch
from
October 6, 2026 22:50
80f6b27 to
f2c1553
Compare
scott-lowe-vapi
force-pushed
the
fix/validate-references
branch
from
October 6, 2026 22:50
1f982af to
908d125
Compare
chris-garber-vapi
approved these changes
Oct 7, 2026
Contributor
Author
Merge activity
|
scott-lowe-vapi
changed the base branch from
ci/validate-resources
to
graphite-base/77
October 7, 2026 18:06
scott-lowe-vapi
added a commit
that referenced
this pull request
Oct 7, 2026
## Value **V.A.L.U.E. tier:** small — touches `.github/workflows/` (a blast-radius path), and changes what customer forks see on their pull requests. - **Problem:** nothing ran `npm run validate` before merge. `apply`, and so promotion, refuses to deploy a config with validation errors: a name over 40 characters, or a per-provider voice schema error. (The validator's other rules, structured-output lockstep, duplicated prompts and the `maxTokens` floor, are warnings and don't fail it.) That refusal came **after** merge: `main` held a config that wouldn't deploy, and promotion stopped until someone opened a fix PR. Plain `push` only warns, then can fail partway with an API 400. Each rule in the validator comes from a real mid-push failure (`improvements.md` #8, #9, #11, #18, #19). - **Who it affects:** every repository on this template, and especially multi-org repos that promote from `main`. - **What changes:** - **A new Validate resources job in `ci.yml`** runs `validate` for every folder under `resources/` on every pull request. It reports every failing org rather than stopping at the first. - **No engine change.** `validate` makes no network call; loading the engine's config only requires a key to be set, so the step sets a placeholder key and an unroutable base URL, so nothing can be sent. The job has no secrets, so it runs the same on forks and Dependabot PRs. - **It's in `ci.yml`, not the PR check workflow,** so every fork gets it without turning on PR checks. On this template, which has no org folders, it does nothing. - **Docs:** - the README quick start; - the `AGENTS.md` change loop: if the check fails, fix the errors and don't weaken the check; - the workflows guide: make it a required check; - the PR checks and troubleshooting guides; - `improvements.md` #37. **Heads-up for forks:** plain `push` only warned about these errors, so a repo may already carry some. The first PR after this lands will show them, whatever it changes. The troubleshooting guide covers it. `apply` already refused those configs, so this moves an existing failure earlier rather than adding a new one. ## Evidence of value `tests/ci-validate-workflow.test.ts` runs the job's real step, read from `ci.yml`, against copies of the starter example: | Case | Result | | --- | --- | | No org folders | passes, "nothing to validate" | | Two valid orgs | passes, both validated | | One org with a 41+ character assistant name, one valid | fails; both validated, the error names only the bad org and the reason ("Vapi caps at 40") | | A folder that isn't a valid org name | fails, naming it | | The job's secrets | none; checkout doesn't persist credentials; the only key is the placeholder | **Mutation:** making the loop ignore `validate`'s exit code fails the two failure-case tests. ## Testing plan - `npm test` (514 tests) and `npx tsc --noEmit` pass. - actionlint, from #75, lints the new job in this PR's CI. - **Not tested:** a customer repo with real resources. The validator itself is unchanged, and `apply` already runs it on every deploy. Refs TEST-141 ## After review - **Install:** `npm ci --ignore-scripts`, which skips the native audio builds `validate` never loads. - **Reproducing locally:** the failure message, the troubleshooting guide and AGENTS.md give a placeholder-key command, so nobody fetches a production key for an offline check. - **Docs:** - `workflows.md` says that requiring the check ties every team's PRs to every org's health, and that merge queues need a `merge_group:` trigger; - CI validates `.ts` resources without `.env.<org>`; - "the same validator `apply` runs" instead of "the same checks". - **Starter example:** its one-sided structured-output link is fixed, and the test asserts it validates with no warnings. - **Tests:** the no-secrets test also checks the job's permissions and that `pull_request_target` isn't a trigger. - **improvements.md #37:** reworded (errors vs. warnings) and carries the PR number. Annotations for warnings come from #77, so I didn't add the `sed` version here. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A reference that names no file and no state entry failed three different ways depending on the field: silently dropped (toolIds, structuredOutputIds), sent raw and rejected mid-push (squad members, hook tools, personalityId, scenarioId), or deferred (improvements.md #31). validate never checked it, so the new CI check couldn't either. - src/validate-refs.ts, run by validate (so by apply and CI) and by push: - dangling-reference (error): a name with no local file and no state entry; - malformed-reference (error): an empty or non-name list entry, which used to crash validate with no file named; - override-tool-by-name (error): toolIds names inside assistantOverrides, membersOverrides or targetOverrides, which push never resolves; the fix points to tools:append, since model.tools there replaces the member's tool set; - unresolved-credential (warning): a credential name not in state, naming the org's bootstrap pull; - reference-by-uuid (warning): only for a UUID this repo tracks, naming the file to use; dashboard-owned UUIDs aren't reported. - One collector (referencesCollect in resolver.ts) feeds both these rules and reference-to-ignored, so judge references to ignored structured outputs are caught; ID cleaning tolerates non-string entries. - validate skips .vapi-ignore'd files, as push does, and now also runs reference-to-ignored. It reads the committed state file offline. - On GitHub Actions, validate prints each finding as an annotation, so it shows on the file in the PR, warnings included. - The validate header no longer prints an API URL for an offline command. - Docs: a rule table in troubleshooting (safe advice for ignored and override references), the commands row, AGENTS.md (never edit state or .vapi-ignore to make a reference resolve), improvements.md #31 marked mitigated with its remaining gaps, and #38 for promotion and stock personalities. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scott-lowe-vapi
force-pushed
the
fix/validate-references
branch
from
October 7, 2026 18:08
908d125 to
49d0272
Compare
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: small — a behavior change:
validate, and soapplyand the Validate resources check, now fail on configs they used to pass.Problem: a reference that names no file fails in one of three ways, depending on the field (
improvements.mddocs: document orphan-YAML gate + --allow-new-files in README and AGENTS #31):model.toolIds,artifactPlan.structuredOutputIds;personalityId,scenarioId;validatenever checked references, so the Validate resources check from ci: validate every org's resources on every pull request #76 couldn't catch a typo'd tool name either. Warnings were also invisible in CI: they don't fail the check, and nobody reads the job log.Who it affects: everyone who edits resource files by hand or with a coding agent, and reviewers of their PRs.
What changes:
New
src/validate-refs.ts, run byvalidate(so byapplyand CI) and bypush:dangling-referenceevaluations[].structuredOutputId.malformed-referencevalidate).override-tool-by-nametoolIdsinsideassistantOverrides,membersOverridesortargetOverrides, where push never resolves names. The fix it gives istools:append, becausemodel.toolsthere replaces the member's tool set.unresolved-credentialreference-by-uuidvalidatenow also runsreference-to-ignored, aspushalready did. It reads the committed state file and stays offline.On GitHub Actions, every finding becomes an annotation, so it shows on the file in the PR, warnings included.
pushreports the new rules alongside its existing validators: warnings by default, blocking under--strict.Docs:
validaterow in the commands guide;AGENTS.md: never edit the state file to make a reference resolve;improvements.mddocs: document orphan-YAML gate + --allow-new-files in README and AGENTS #31 marked resolved by validation.Evidence of value
The starter example with two typos,
scheduler→schedularin the squad andbooking-confirmed→booking-confirmdin a judge:npm run validate0 error(s)— ✅ Validation passed2 error(s), onedangling-referenceper typo, naming the file and the missing nametests/validate-refs.test.tscovers:%, newlines,:and,is tested intests/validate.test.ts.tests/ci-validate-workflow.test.tsruns the CI step withGITHUB_ACTIONS=trueon the typo'd squad. The step fails, and the::errorpoints atresources/clinic/squads/front-desk.yml.unresolved-credentialwarning, which is accurate.Testing plan
npm test(523 tests) andnpx tsc --noEmitpass.applyvalidates before it pulls. So a reference to a resource created in the dashboard and never pulled now stopsapply; the message says to pull first. Before,applywent on to pull and push, and the reference resolved only if the pull happened to produce that exact name.applyorpush. Neither code path changed except for the added findings.Refs TEST-141
After review
validateskips.vapi-ignored files, as push does, so ignored files can't fail CI orapply.referencesCollectinresolver.tsfeeds bothreference-to-ignoredand these rules, so a judge that references an ignored structured output is caught.?.uuid, soconstructorand friends don't resolve..vapi-ignoreto make a reference resolve.toolRefs, inline members, annotation lines). feat(drift): three-way drift-direction classifier + content-drift audit + canonicalization fixes #38 records the promotion and stock-personality limitation found in feat(promotion): gate promotion out of an org on a passing check #66.Follow-ups: line numbers on annotations, the wider override walk, and deduplicating the credential walkers.
🤖 Generated with Claude Code