CedarCopilot wants to merge 1 commit into staging from fix/master-surface-tool-prototype-clone
Live on prod, no production signal yet
No positive runtime evidence was found for the intended master-family behavior in staging after 2026-09-08T20:53:20Z. Exact OTEL queries confirmed 0 spans for find-crm-conversations, get-calendar-availability, and save-draft; the post-deploy execute_tool inventory contained.
internal_only
internal_only
internal_only
internal_only
internal_only
internal_only
internal_only
internal_only
Libra has verdicts on 0 of 1 tracked behaviors on prod; 1 is still being checked. Libra checks hourly for 3 days after each deploy.
withApprovalContext (master-surface.ts, added in 31fa01668, 2026-09-07) returned
{ ...tool, execute: wrapped } , a plain-object spread that drops the tool's prototype.
Mastra's own dispatcher (isVercelTool in @mastra/core) decides how to call a tool by
instanceof Tool; a tool that fails that check gets invoked with (args, execOptions)
instead of (args, toolContext), and only the latter carries requestContext.
Every tool in buildMasterFamilyToolset() , find-crm-conversations,
get-calendar-availability, save-draft, resolve-and-attach, query-conversations, and
every other family/orchestration tool , silently lost requestContext this way, for every
account on the cedar-master-agent-families flag. Symptom: "Missing userId in runtime context" on effectively every tool call in chat (except load-skill, which is a
constructor tool on the agent and never reaches this wrapper).
This is the identical bug PR #2701 (bde571e7c) fixed one day earlier in a sibling
wrapper, gateToolExecute (tool-grant.ts) , reintroduced here because withApprovalContext
was written fresh instead of reusing the shared cloneToolWithExecute() helper that fix
extracted for exactly this reason. It shipped in the 2026-09-08 19:10 UTC deploy and was live
for at least the following hour: traced via Sherlock across 3 unrelated orgs (Corgi, Pirros,
Strac) and 12+ tool failures.
master-surface.ts: withApprovalContext now returns cloneToolWithExecute(tool, wrapped)
instead of the raw spread , same pattern already used by gateToolExecute and
instrumentToolExecute.master-surface.test.ts: added an instanceof Tool assertion over the live
buildMasterFamilyToolset() registry (mirrors the regression test PR #2701 added for
gateToolExecute), so any future family/orchestration tool is covered automatically.no-raw-tool-spread.test.ts (new): a repo-wide static scan of every non-test file under
apps/server/src/mastra/ for the { ...tool, execute: } shape. There's no live registry of
"every function that overrides a tool's execute" the way FAMILY_TOOLS is a registry of
tools themselves, so a source-text scan is the only place to catch a new wrapper before
it's ever wired into anything and shipped , this is the guard against a third instance of
the same mistake.pnpm --filter @zero/server exec vitest run src/mastra/tools/__tests__/master-surface.test.ts src/mastra/tools/__tests__/tool-grant.test.ts src/mastra/tools/__tests__/no-raw-tool-spread.test.ts , all pass.master-surface.ts fails no-raw-tool-spread.test.ts with a clear message pointing at
cloneToolWithExecute; restoring the fix passes again.pnpm --filter @zero/server run types , clean."Missing userId in runtime context" / "No userId in context."
rows in chat_messages for accounts on cedar-master-agent-families after this deploys.🤖 Generated with Claude Code
https://claude.ai/code/session_011jWjgeVGqiM5Fibxe89oT5
This PR repairs the family-tool wrapper so wrapped Mastra tools retain their prototype and therefore continue receiving request context. It also adds behavioral and source-level regression coverage.
cloneToolWithExecute.Tool.The production fix appears safe to merge; the remaining concern is a non-blocking limitation in the new static regression guard.
The shared cloning helper preserves the Mastr
Libra has not measured any production surfaces for this change yet.