Skip to content

Enforce authoritative Gmail profile over model-chosen profile in tool calls#287

Merged
witbrock merged 1 commit into
mainfrom
gmail-profile-enforce-authoritative
Jun 14, 2026
Merged

Enforce authoritative Gmail profile over model-chosen profile in tool calls#287
witbrock merged 1 commit into
mainfrom
gmail-profile-enforce-authoritative

Conversation

@witbrock

Copy link
Copy Markdown
Member

Problem (observed live)

A turn ("Tell me what I should do with my last 8 emails") failed because qwen3:8b emitted gmail_list_messages with profile: "zhan-gmail" — an account whose OAuth refresh token is expired/revoked (invalid_grant … RefreshError) — even though vonwitbrock-gmail was selected and "Test Gmail access" passes in the settings UI. The call hard-failed and the whole turn failed (decision: failed; it never reached task_create).

Root cause: apply_runtime_defaults_to_mcp_payload only filled the gmail profile when the model left it blank or as a "default"/"primary" placeholder. An explicit profile the model chose itself passed straight through — so the model, not the user's selection, decided which Gmail account (credential) to use, and picked a dead one.

Change

Which Gmail account to use is an identity/credential decision the caller's selection owns, not the model. For gmail_* tools, enforce the authoritative profile (request-selected — already resolved request-first at the orchestrator merge — else the configured default) over any supplied profile that differs, including an explicit model-chosen one.

Safety: when no authoritative default is resolved (default_gmail_profile is None, e.g. deterministic/scheduled workflows), the supplied value is left untouched — so workflow steps that set a profile deterministically are unaffected. Telemetry distinguishes default_gmail_profile (blank fill) / …_placeholder_replacement / …_enforced_override and records the overridden profile.

This does not depend on any .env value: it enforces whatever authoritative profile is resolved (request selection preferred, env default as fallback).

Tests

tests/backend/test_mcp_tool_bridge_gmail_profile.py (6): override of an explicit wrong profile, blank fill, placeholder replacement, no-op when matching, no enforcement when default is None (deterministic-workflow safety), and gmail-only scoping. Green.

Notes / not in scope

  • Latency: the same turn also spent ~15.5 min in a single qwen3:8b generation — a separate model/host perf issue.
  • Residual gap (follow-up): if there is genuinely no authoritative default and the model supplies a profile, it is still used. Options: fall back to the sole configured profile, or exclude profiles with expired/revoked tokens from the model's choices.

🤖 Generated with Claude Code

…ol calls

apply_runtime_defaults_to_mcp_payload only filled a blank or "default"/"primary"
placeholder profile for gmail_* tools, so an explicit profile the MODEL chose
passed straight through. Observed live: qwen3:8b emitted gmail_list_messages
with profile="zhan-gmail" (an account whose OAuth refresh token is
expired/revoked) even though the valid "vonwitbrock-gmail" profile was selected
and tests green in the settings UI. The call hard-failed with invalid_grant and
the whole turn failed.

Which Gmail account to use is an identity/credential decision the caller's
selection owns, not the model. Enforce the authoritative profile (request-
selected, resolved request-first at orchestrator merge, else the configured
default) over ANY supplied profile that differs. When no authoritative default
is resolved (default_gmail_profile is None, e.g. deterministic/scheduled
workflows) the supplied value is left untouched, so this does not disturb
workflow steps that set a profile deterministically. Telemetry distinguishes
blank-fill / placeholder-replacement / enforced-override and records the
overridden profile.

Tests: tests/backend/test_mcp_tool_bridge_gmail_profile.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@witbrock witbrock merged commit 5d8944c into main Jun 14, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant