fix: stop handoff tools 400ing on a first push into an empty org - #51
Open
dhruva-vapi wants to merge 2 commits into
Open
fix: stop handoff tools 400ing on a first push into an empty org#51dhruva-vapi wants to merge 2 commits into
dhruva-vapi wants to merge 2 commits into
Conversation
Tools are applied before assistants, because assistants reference tools. A handoff/transfer tool references an assistant, inverting the dependency for that subset — a genuine cycle. The create path already handled it: unresolved assistant destinations are stripped, and updateToolAssistantRefs links them once every assistant exists. The update path sent the raw slug instead, so the API answered 400 "Assistant with ID <slug> not found" and the push aborted before the linking pass could run. The update payload now omits the whole destinations key when any entry is unresolved. Omitting rather than filtering matters: PATCH replaces the keys it receives, so a filtered array would wipe destinations that are live on the dashboard whenever the referenced assistant is merely untracked locally.
dhruva-vapi
force-pushed
the
fix/tool-assistant-circular-dep
branch
from
August 1, 2026 19:17
a3ec456 to
3437e60
Compare
A both-diverged report showed three 8-character hash prefixes and nothing else, which tells an operator nothing about which side to keep. Each entry now also carries the dashboard's updatedAt, the local file's mtime, and which is newer. It stays advisory and the engine still refuses to choose, because none of these timestamps is authoritative: - updatedAt is bumped by our own pushes, so a newer dashboard often just means you pushed recently rather than that a teammate changed something. - local mtime is reset by git clone and git checkout, so on a fresh checkout every file looks edited seconds ago. - later does not mean supersedes. An edit to the prompt and an edit to the voice both deserve to survive; last-write-wins would discard one silently. Sub-minute gaps report as within a minute of each other rather than naming a winner, since clock skew is the same order of magnitude as the gap. Nothing is persisted: a previous schema stored lastPulledAt and it was deliberately dropped in favour of content hashes.
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.
Linear ticket
https://linear.app/vapi/issue/PRISM-1382/gitops-fix-handoff-tools-400ing-on-a-first-push-into-an-empty-org
Fixes PRISM-1382
Value
A first push into an empty org failed outright whenever the repo contained a handoff or transfer tool:
The push aborted partway, leaving the org half-configured, and the error named an assistant rather than the tool — so the cause read as an assistant problem when it was a push-ordering one. Onboarding a customer now works on the first push instead of requiring someone to know the two-step workaround.
Tools are applied before assistants, because assistants reference tools. A handoff tool references an assistant, which inverts the dependency for that subset — a genuine cycle. The engine already resolved it in two passes; one half of that machinery was missing.
The cycle, and how the two passes break it
The second pass already existed. Only the create path stripped unresolved destinations, so the update path sent a raw slug and died before the linking pass could run.
Note on scope: #52 (advisory conflict timing) merged into this branch as part of the stack, so this PR's diff now also carries
src/pull.tsconflict-report changes,tests/conflict-timing.test.ts, and async-behavior.mdsection that the description above does not cover. That change has its own description, diagram, and rationale in #52 — it is not new or unreviewed work, it simply landed one level down the stack rather than intomain.Evidence of value
The create path already stripped unresolved assistant destinations, and
updateToolAssistantRefsalready PATCHes the real destinations once every assistant exists. The update path did not: it sent the raw slug, the API rejected it, and because the tool apply rethrows, the run aborted before the linking pass could execute. The update payload now omits the wholedestinationskey when any entry is unresolved, so the existing linking pass sets the real value.Omitting rather than filtering is the load-bearing choice. Vapi PATCH replaces the keys it receives, so sending a filtered array would wipe destinations that are live on the dashboard whenever the referenced assistant is merely untracked locally — a
--type toolspush, for instance. An absent key is left untouched.Six new tests in
tests/tool-assistant-cycle.test.ts(plus seven from #52 intests/conflict-timing.test.ts) cover an unresolved reference, a resolved one, a reference carrying a trailing YAML comment, a partially-resolved array (must omit, not filter), a tool with no destinations, and non-assistant destination types.API Changes (including webhooks + events)
Is this changing the public API?
If yes, is it backward‐compatible?
Non backward-compatible changes might break customers' agents. Please proceed with care and notify the team.
Testing plan
npm run buildandnpm testare green (the 20 failures in the suite are pre-existing onmainand unrelated —upsertStatemerge semantics).Verification that matters most is a live one: push a repo containing a handoff tool into a fresh empty org and confirm the run completes, then confirm the tool's
destinationson the dashboard point at the correct assistant UUID after the linking pass. Until this merges, the workaround isnpm run push -- <org> --type assistantsfollowed by a full push.Regression signal: any
400 Assistant with ID "<slug>" not foundon a tool PATCH, or a handoff tool whose dashboard destinations are empty after a successful push.