Repository navigation
ci: validate every org's resources on every pull request - #76
Merged
Merged
Conversation
This was referenced Oct 3, 2026
Merged
Contributor
Author
scott-lowe-vapi
marked this pull request as ready for review
October 3, 2026 07:29
This was referenced Oct 3, 2026
chris-garber-vapi
left a comment
There was a problem hiding this comment.
I think running validate in CI makes sense
chris-garber-vapi
approved these changes
Oct 6, 2026
chris-garber-vapi
left a comment
There was a problem hiding this comment.
Review of the Validate resources job. Nothing here blocks merge (no 🔴 or 🟠). The main themes: the docs overstate what fails (3 of the 5 named rules are warnings, and they stay hidden), the local repro path needs a real org key, env-dependent .ts resources validate differently in CI than under apply, and every org being validated on every PR widens the blast radius. Suggestion blocks were run locally against the PR head unless noted.
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
ci/workflow-hardening
branch
from
October 6, 2026 22:50
69959a3 to
1aa43fe
Compare
Contributor
Author
Merge activity
|
scott-lowe-vapi
changed the base branch from
ci/workflow-hardening
to
graphite-base/76
October 7, 2026 18:04
Nothing ran `npm run validate` before merge. A config that `apply` refuses (a name over 40 characters, a per-provider voice schema error) could merge green, and deploys and promotion out of main then stopped until a fix landed. Plain `push` only warns, and can fail partway with an API 400. The validator's other rules (structured-output lockstep, duplicated prompts, the maxTokens floor) are warnings and don't fail it. - ci.yml gets a Validate resources job: validate for every folder under resources/, reporting every failing org rather than stopping at the first. validate makes no network call; the engine's config only needs 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 and installs without scripts, so forks get it too. No engine change. - The failure message, troubleshooting guide and AGENTS.md give a placeholder-key command to reproduce it, so nobody fetches a real key for an offline check. - The docs say what requiring it costs (every org gates every PR), the merge-queue trigger it needs, and that CI validates .ts resources without .env.<org>. - The starter example's one-sided structured-output link is fixed, so it validates without warnings. - tests/ci-validate-workflow.test.ts runs the step itself against fixture orgs: no orgs, all valid (and warning-free), one invalid org among valid ones, an invalid folder name, and no secrets, permissions, privileged trigger or persisted credentials. - README, AGENTS.md (change loop), the workflows, PR checks and troubleshooting guides, and improvements.md #37 describe it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scott-lowe-vapi
force-pushed
the
ci/validate-resources
branch
from
October 7, 2026 18:06
f2c1553 to
e6da939
Compare
scott-lowe-vapi
added a commit
that referenced
this pull request
Oct 7, 2026
) ## Value **V.A.L.U.E. tier:** small — a behavior change: `validate`, and so `apply` and 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.md` #31): - it's **silently dropped**: `model.toolIds`, `artifactPlan.structuredOutputIds`; - it's **sent raw and rejected partway through a push**: squad members, hook tools, `personalityId`, `scenarioId`; - or it's **deferred** to a linking pass. `validate` never checked references, so the Validate resources check from #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 by `validate` (so by `apply` and CI) and by `push`: | Rule | Severity | Catches | | --- | --- | --- | | `dangling-reference` | error | A name with no local file and no state entry. Uses the shared reference walk, plus scenario judges' `evaluations[].structuredOutputId`. | | `malformed-reference` | error | An empty or non-name entry in a reference list (it used to crash `validate`). | | `override-tool-by-name` | error | A tool name in `toolIds` inside `assistantOverrides`, `membersOverrides` or `targetOverrides`, where push never resolves names. The fix it gives is `tools:append`, because `model.tools` there replaces the member's tool set. | | `unresolved-credential` | warning | A credential name missing from the state file. The message names the org's bootstrap pull. | | `reference-by-uuid` | warning | A UUID for a resource this repo tracks, which breaks promotion; it names the file to use. UUIDs the repo doesn't track (dashboard-owned, stock personalities) aren't reported. | - **`validate` now also runs `reference-to-ignored`,** as `push` already 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. - **`push`** reports the new rules alongside its existing validators: warnings by default, blocking under `--strict`. - **Docs:** - a rule-by-rule table in troubleshooting; - the `validate` row in the commands guide; - `AGENTS.md`: never edit the state file to make a reference resolve; - `improvements.md` #31 marked resolved by validation. ## Evidence of value The starter example with two typos, `scheduler` → `schedular` in the squad and `booking-confirmed` → `booking-confirmd` in a judge: | | Before (#76) | After | | --- | --- | --- | | `npm run validate` | `0 error(s)` — ✅ Validation passed | `2 error(s)`, one `dangling-reference` per typo, naming the file and the missing name | - **New `tests/validate-refs.test.ts`** covers: - names that resolve to local files and to state; - a typo in each reference field; - ignored references; - UUIDs and stock personalities; - all three override keys; - credentials as names, as UUIDs, and known to state. - **Annotation format:** escaping of `%`, newlines, `:` and `,` is tested in `tests/validate.test.ts`. - **End to end:** `tests/ci-validate-workflow.test.ts` runs the CI step with `GITHUB_ACTIONS=true` on the typo'd squad. The step fails, and the `::error` points at `resources/clinic/squads/front-desk.yml`. - **Mutation:** dropping the judge collection or the override walk fails two tests. - **Every example org still passes.** The cross-org promotion example (which has no state files) now shows an `unresolved-credential` warning, which is accurate. ## Testing plan - `npm test` (523 tests) and `npx tsc --noEmit` pass. - **Behavior to expect:** `apply` validates before it pulls. So a reference to a resource created in the dashboard and never pulled now stops `apply`; the message says to pull first. Before, `apply` went on to pull and push, and the reference resolved only if the pull happened to produce that exact name. - **Not tested:** a live `apply` or `push`. Neither code path changed except for the added findings. - **Not in this PR:** a check for secrets committed in resource files. It goes in its own PR, because a false positive there would block a customer's merge. Refs TEST-141 ## After review - **Ignored files:** `validate` skips `.vapi-ignore`d files, as push does, so ignored files can't fail CI or `apply`. - **One collector:** `referencesCollect` in `resolver.ts` feeds both `reference-to-ignored` and these rules, so a judge that references an ignored structured output is caught. - **Lookups:** use `?.uuid`, so `constructor` and friends don't resolve. - **Troubleshooting:** the table gives safe advice for ignored and override references, and AGENTS.md says never to edit the state file or `.vapi-ignore` to make a reference resolve. - **improvements.md:** #31 is marked mitigated, with the remaining gaps listed (unchecked override paths, `toolRefs`, inline members, annotation lines). #38 records the promotion and stock-personality limitation found in #66. Follow-ups: line numbers on annotations, the wider override walk, and deduplicating the credential walkers. 🤖 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: small — touches
.github/workflows/(a blast-radius path), and changes what customer forks see on their pull requests.npm run validatebefore 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 themaxTokensfloor, are warnings and don't fail it.) That refusal came after merge:mainheld a config that wouldn't deploy, and promotion stopped until someone opened a fix PR. Plainpushonly warns, then can fail partway with an API 400. Each rule in the validator comes from a real mid-push failure (improvements.mdmembersOverrides.artifactPlan.structuredOutputIds is requiring UUID #8, Specifying handoff tools in a squad requires UUID to function correctly #9, fix(call): clear wrapped partial transcripts cleanly in npm run call #11, feat: simulation suite runner (npm run sim) #18, refactor: state schema with per-resource content hashes #19).main.ci.ymlrunsvalidatefor every folder underresources/on every pull request. It reports every failing org rather than stopping at the first.validatemakes 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.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.AGENTS.mdchange loop: if the check fails, fix the errors and don't weaken the check;improvements.mdAdd GitOps Support for Pronunciation Dictionaries with Versioned Updates #37.Heads-up for forks: plain
pushonly 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.applyalready refused those configs, so this moves an existing failure earlier rather than adding a new one.Evidence of value
tests/ci-validate-workflow.test.tsruns the job's real step, read fromci.yml, against copies of the starter example:Mutation: making the loop ignore
validate's exit code fails the two failure-case tests.Testing plan
npm test(514 tests) andnpx tsc --noEmitpass.applyalready runs it on every deploy.Refs TEST-141
After review
npm ci --ignore-scripts, which skips the native audio buildsvalidatenever loads.workflows.mdsays that requiring the check ties every team's PRs to every org's health, and that merge queues need amerge_group:trigger;.tsresources without.env.<org>;applyruns" instead of "the same checks".pull_request_targetisn't a trigger.Annotations for warnings come from #77, so I didn't add the
sedversion here.🤖 Generated with Claude Code