Skip to content

fix(google-oauth): set correct OAuth redirect URI for workspace-mcp in runner pods#1480

Merged
mergify[bot] merged 2 commits intomainfrom
fix/google-oauth-redirect-uri-localhost
Apr 29, 2026
Merged

fix(google-oauth): set correct OAuth redirect URI for workspace-mcp in runner pods#1480
mergify[bot] merged 2 commits intomainfrom
fix/google-oauth-redirect-uri-localhost

Conversation

@markturansky
Copy link
Copy Markdown
Contributor

@markturansky markturansky commented Apr 29, 2026

Summary

  • Root cause: workspace-mcp defaults to http://localhost:8000/oauth2callback for its OAuth redirect URI when GOOGLE_OAUTH_REDIRECT_URI is not set. Google rejects this with redirect_uri_mismatch because localhost:8000 is not registered in the Google Cloud Console OAuth app.
  • Why it regressed: The alpha migration PR (feat(runner,manifests): alpha migration PR 6+7 — runners and Kustomize overlays #1379) rewrote credential fetching but did not carry forward GOOGLE_OAUTH_REDIRECT_URI configuration, leaving workspace-mcp to fall back to its localhost:8000 default when credentials are absent or expired.
  • Fix: Thread the backend's public URL through the operator so runner pods receive GOOGLE_OAUTH_REDIRECT_URI=${BACKEND_PUBLIC_URL}/oauth2callback, which points to the already-registered and already-handled /oauth2callback endpoint in handlers/oauth.go.

Changes

File Change
operator/internal/config/config.go Add BackendPublicURL field; read from BACKEND_PUBLIC_URL env var
operator/internal/handlers/sessions.go Conditionally inject GOOGLE_OAUTH_REDIRECT_URI into runner pod env
manifests/base/core/operator-deployment.yaml Source BACKEND_PUBLIC_URL from google-workflow-app-secret key BACKEND_URL (optional) — same secret already used for OAuth client credentials
runners/ambient-runner/.mcp.json Forward GOOGLE_OAUTH_REDIRECT_URI into the workspace-mcp process env

Admin action required

No new secrets are needed. The BACKEND_URL key already exists in google-workflow-app-secret (it's sourced by the backend deployment). The operator will now read it too — no changes needed if that secret is already populated.

If BACKEND_PUBLIC_URL is not set, the conditional block in sessions.go is a no-op and behavior is unchanged (degrades gracefully).

Test plan

  • Restart the operator after deploying to pick up BACKEND_PUBLIC_URL
  • Start a new session and trigger a Google Workspace tool (e.g., send email, list Drive files)
  • Verify no redirect_uri_mismatch error from Google
  • Verify the OAuth consent URL (if shown) contains the backend domain, not localhost:8000
  • Confirm go build ./... passes in components/operator/

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added SDK types and builders for Scheduled Sessions, enabling creation, listing, and patching of scheduled agent sessions.
    • Added support for configuring a public backend URL to ensure correct OAuth redirect URIs in runner pods.
  • Improvements

    • Operator and runtime configurations now propagate public backend URL and OAuth redirect settings to workspace integration services for reliable Google Workspace authentication.

…er pods

workspace-mcp defaults to http://localhost:8000/oauth2callback when
GOOGLE_OAUTH_REDIRECT_URI is not set. This causes Google OAuth to fail
with "redirect_uri_mismatch" because localhost:8000 is not a registered
redirect URI in the Google Cloud Console app.

The fix threads the backend's public URL through the operator so that
runner pods receive the correct redirect URI pointing to the Ambient
backend's /oauth2callback endpoint (which is already registered and
handled by handlers/oauth.go).

Changes:
- operator/config.go: add BackendPublicURL field, read from BACKEND_PUBLIC_URL env
- operator/handlers/sessions.go: inject GOOGLE_OAUTH_REDIRECT_URI into
  runner pod env when BackendPublicURL is configured
- manifests/base/core/operator-deployment.yaml: source BACKEND_PUBLIC_URL
  from google-workflow-app-secret (key: BACKEND_URL, optional) — reuses
  the same secret already configured by platform admins
- runners/ambient-runner/.mcp.json: forward GOOGLE_OAUTH_REDIRECT_URI
  into the workspace-mcp process environment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 29, 2026

Deploy Preview for cheerful-kitten-f556a0 canceled.

Name Link
🔨 Latest commit 1c2c067
🔍 Latest deploy log https://app.netlify.com/projects/cheerful-kitten-f556a0/deploys/69f2359c25199400089afd7c

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

Adds a propagated backend public URL: secret-sourced env var is added to the operator deployment, read into operator config, used to set GOOGLE_OAUTH_REDIRECT_URI in runners; also adds scheduled session types to the Go SDK and minor MCP JSON arg/env formatting changes.

Changes

Cohort / File(s) Summary
Operator manifests & deployment
components/manifests/base/core/operator-deployment.yaml
Adds BACKEND_PUBLIC_URL env var in the agentic-operator container sourced from google-workflow-app-secret key BACKEND_URL (optional).
Operator config & handlers
components/operator/internal/config/config.go, components/operator/internal/handlers/sessions.go
Adds BackendPublicURL string to operator Config; LoadConfig() reads BACKEND_PUBLIC_URL. sessions.go conditionally injects GOOGLE_OAUTH_REDIRECT_URI into runner env when BackendPublicURL is set.
MCP server config
components/runners/ambient-runner/.mcp.json
Reformats args arrays and splits --kubeconfig flag/path into separate entries; adds GOOGLE_OAUTH_REDIRECT_URI env entry for mcpServers.google-workspace.
Ambient SDK — scheduled sessions types
components/ambient-sdk/go-sdk/types/scheduled_session.go
Adds ScheduledSession, ScheduledSessionList, ScheduledSessionPatch, builders (ScheduledSessionBuilder, ScheduledSessionPatchBuilder) and associated accessors/validation methods for scheduled session CRUD and patch semantics.
🚥 Pre-merge checks | ✅ 7 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (7 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title follows Conventional Commits format (fix scope) and accurately describes the primary change: setting the correct OAuth redirect URI for workspace-mcp in runner pods.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Performance And Algorithmic Complexity ✅ Passed PR introduces no meaningful performance regressions; OAuth redirect URI injection is O(1), ScheduledSession builders perform constant-time validation, no O(n²)+ algorithms or N+1 patterns detected.
Security And Secret Handling ✅ Passed PR does not introduce new secrets/tokens logged plaintext or hardcoded. BACKEND_PUBLIC_URL sourced from existing K8s secret, no SQL/injection vulnerabilities.
Kubernetes Resource Safety ✅ Passed PR modifications are limited to configuration management with proper resource requests/limits, scoped RBAC, and no new security context issues.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/google-oauth-redirect-uri-localhost
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/google-oauth-redirect-uri-localhost

Review rate limit: 9/10 reviews remaining, refill in 6 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/operator/internal/handlers/sessions.go`:
- Around line 1083-1087: The redirect URI concatenation is fragile; normalize
appConfig.BackendPublicURL by trimming whitespace and any trailing slashes
before appending "/oauth2callback" to avoid double or missing slashes. Update
the code that builds the EnvVar (the Value for "GOOGLE_OAUTH_REDIRECT_URI" where
appConfig.BackendPublicURL is used) to call strings.TrimSpace then
strings.TrimRight(baseURL, "/") (or equivalent) and then construct the final
value with baseURL + "/oauth2callback"; remember to add the strings import if
missing.
- Around line 1083-1088: When the code decides not to inject
GOOGLE_OAUTH_REDIRECT_URI because appConfig.BackendPublicURL is empty, add a
warning so misconfiguration is visible: detect when Google OAuth client
configuration is present (e.g., GOOGLE_OAUTH_CLIENT_ID and/or
GOOGLE_OAUTH_CLIENT_SECRET in the environment or the corresponding appConfig
fields) and when appConfig.BackendPublicURL == "" emit a warning log (use the
same logger/context used in this handler) stating that BACKEND_PUBLIC_URL is
empty and redirect URI injection is skipped which may cause
redirect_uri_mismatch at runtime; keep the existing conditional that appends
GOOGLE_OAUTH_REDIRECT_URI to base unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 69888b2e-ebe3-492e-8b03-bab483fe2dc0

📥 Commits

Reviewing files that changed from the base of the PR and between d347d53 and 5c8a944.

📒 Files selected for processing (4)
  • components/manifests/base/core/operator-deployment.yaml
  • components/operator/internal/config/config.go
  • components/operator/internal/handlers/sessions.go
  • components/runners/ambient-runner/.mcp.json

Comment on lines +1083 to +1087
if appConfig.BackendPublicURL != "" {
base = append(base, corev1.EnvVar{
Name: "GOOGLE_OAUTH_REDIRECT_URI",
Value: fmt.Sprintf("%s/oauth2callback", appConfig.BackendPublicURL),
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Normalize the redirect base URL before concatenation.

fmt.Sprintf("%s/oauth2callback", appConfig.BackendPublicURL) is fragile: a trailing slash or whitespace in BACKEND_PUBLIC_URL can produce a mismatched redirect URI and break OAuth.

Proposed fix
-				if appConfig.BackendPublicURL != "" {
+				redirectBase := strings.TrimRight(strings.TrimSpace(appConfig.BackendPublicURL), "/")
+				if redirectBase != "" {
 					base = append(base, corev1.EnvVar{
 						Name:  "GOOGLE_OAUTH_REDIRECT_URI",
-						Value: fmt.Sprintf("%s/oauth2callback", appConfig.BackendPublicURL),
+						Value: fmt.Sprintf("%s/oauth2callback", redirectBase),
 					})
 				}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if appConfig.BackendPublicURL != "" {
base = append(base, corev1.EnvVar{
Name: "GOOGLE_OAUTH_REDIRECT_URI",
Value: fmt.Sprintf("%s/oauth2callback", appConfig.BackendPublicURL),
})
redirectBase := strings.TrimRight(strings.TrimSpace(appConfig.BackendPublicURL), "/")
if redirectBase != "" {
base = append(base, corev1.EnvVar{
Name: "GOOGLE_OAUTH_REDIRECT_URI",
Value: fmt.Sprintf("%s/oauth2callback", redirectBase),
})
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/operator/internal/handlers/sessions.go` around lines 1083 - 1087,
The redirect URI concatenation is fragile; normalize appConfig.BackendPublicURL
by trimming whitespace and any trailing slashes before appending
"/oauth2callback" to avoid double or missing slashes. Update the code that
builds the EnvVar (the Value for "GOOGLE_OAUTH_REDIRECT_URI" where
appConfig.BackendPublicURL is used) to call strings.TrimSpace then
strings.TrimRight(baseURL, "/") (or equivalent) and then construct the final
value with baseURL + "/oauth2callback"; remember to add the strings import if
missing.

Comment on lines +1083 to +1088
if appConfig.BackendPublicURL != "" {
base = append(base, corev1.EnvVar{
Name: "GOOGLE_OAUTH_REDIRECT_URI",
Value: fmt.Sprintf("%s/oauth2callback", appConfig.BackendPublicURL),
})
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add an explicit warning when redirect URI injection is skipped.

When Google OAuth client vars are present but BACKEND_PUBLIC_URL is empty, the runner can still fall back to localhost behavior and fail with redirect_uri_mismatch at runtime. Emitting a warning here makes misconfiguration immediately visible.

Proposed fix
-				if appConfig.BackendPublicURL != "" {
+				redirectBase := strings.TrimRight(strings.TrimSpace(appConfig.BackendPublicURL), "/")
+				if redirectBase != "" {
 					base = append(base, corev1.EnvVar{
 						Name:  "GOOGLE_OAUTH_REDIRECT_URI",
-						Value: fmt.Sprintf("%s/oauth2callback", appConfig.BackendPublicURL),
+						Value: fmt.Sprintf("%s/oauth2callback", redirectBase),
 					})
+				} else if strings.TrimSpace(os.Getenv("GOOGLE_OAUTH_CLIENT_ID")) != "" &&
+					strings.TrimSpace(os.Getenv("GOOGLE_OAUTH_CLIENT_SECRET")) != "" {
+					log.Printf("Warning: BACKEND_PUBLIC_URL is empty; GOOGLE_OAUTH_REDIRECT_URI will not be set and workspace-mcp may default to localhost callback")
 				}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/operator/internal/handlers/sessions.go` around lines 1083 - 1088,
When the code decides not to inject GOOGLE_OAUTH_REDIRECT_URI because
appConfig.BackendPublicURL is empty, add a warning so misconfiguration is
visible: detect when Google OAuth client configuration is present (e.g.,
GOOGLE_OAUTH_CLIENT_ID and/or GOOGLE_OAUTH_CLIENT_SECRET in the environment or
the corresponding appConfig fields) and when appConfig.BackendPublicURL == ""
emit a warning log (use the same logger/context used in this handler) stating
that BACKEND_PUBLIC_URL is empty and redirect URI injection is skipped which may
cause redirect_uri_mismatch at runtime; keep the existing conditional that
appends GOOGLE_OAUTH_REDIRECT_URI to base unchanged.

The scheduledSessions plugin and ambient-sdk go-sdk referenced
openapi.ScheduledSession, openapi.ScheduledSessionList,
openapi.ScheduledSessionPatchRequest, and types.ScheduledSession /
types.ScheduledSessionPatch / types.ScheduledSessionList which were
never generated. This caused ambient-api-server and ambient-control-plane
Docker builds to fail with "undefined" compile errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/ambient-sdk/go-sdk/types/scheduled_session.go`:
- Around line 90-104: The Build methods are returning pointers to internal
mutable builder fields (e.g., ScheduledSessionBuilder.Build returning
&b.resource), allowing later builder calls to mutate previously built objects;
change Build (and the analogous builder method that returns &b.patch) to return
a pointer to a copy instead of the internal field by creating a local copy of
b.resource (or b.patch) and returning its address so the builder's internal
state remains immutable to callers.
- Around line 46-49: ScheduledSessionBuilder currently keeps a persistent errs
slice on the struct which is appended across Build() calls; change validation to
use a local errs variable inside the Build() method (remove reliance on the
builder's errs field) so each Build() starts fresh. Locate
ScheduledSessionBuilder and its Build() method and: 1) make errs a local []error
in Build(); 2) update any validation helpers called by Build() (or their call
sites) to return errors (or append to the local errs) instead of mutating
b.errs; and 3) keep the resource population logic (resource field) as-is but
only return accumulated local errs from Build() so prior failures don't persist
across builds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 33f6ac0d-a342-4dc0-8a31-828fc50c33ad

📥 Commits

Reviewing files that changed from the base of the PR and between 5c8a944 and 1c2c067.

⛔ Files ignored due to path filters (3)
  • components/ambient-api-server/pkg/api/openapi/model_scheduled_session.go is excluded by !**/pkg/api/openapi/**
  • components/ambient-api-server/pkg/api/openapi/model_scheduled_session_list.go is excluded by !**/pkg/api/openapi/**
  • components/ambient-api-server/pkg/api/openapi/model_scheduled_session_patch_request.go is excluded by !**/pkg/api/openapi/**
📒 Files selected for processing (1)
  • components/ambient-sdk/go-sdk/types/scheduled_session.go

Comment on lines +46 to +49
type ScheduledSessionBuilder struct {
resource ScheduledSession
errs []error
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Builder validation errors persist across Build() calls

errs is stored on the builder and appended on each Build(). After one failed call, later calls can keep failing even when inputs are fixed (Line 100). Make validation errors local to Build().

Suggested fix
 type ScheduledSessionBuilder struct {
 	resource ScheduledSession
-	errs     []error
 }
@@
 func (b *ScheduledSessionBuilder) Build() (*ScheduledSession, error) {
+	var errs []error
 	if b.resource.Name == "" {
-		b.errs = append(b.errs, fmt.Errorf("name is required"))
+		errs = append(errs, fmt.Errorf("name is required"))
 	}
 	if b.resource.AgentID == "" {
-		b.errs = append(b.errs, fmt.Errorf("agent_id is required"))
+		errs = append(errs, fmt.Errorf("agent_id is required"))
 	}
 	if b.resource.Schedule == "" {
-		b.errs = append(b.errs, fmt.Errorf("schedule is required"))
+		errs = append(errs, fmt.Errorf("schedule is required"))
 	}
-	if len(b.errs) > 0 {
-		return nil, fmt.Errorf("validation failed: %w", errors.Join(b.errs...))
+	if len(errs) > 0 {
+		return nil, fmt.Errorf("validation failed: %w", errors.Join(errs...))
 	}
 	return &b.resource, nil
 }

As per coding guidelines, **/*: - Prioritize Critical and Major severity issues. Minimize Minor and Trivial findings.

Also applies to: 90-102

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-sdk/go-sdk/types/scheduled_session.go` around lines 46 -
49, ScheduledSessionBuilder currently keeps a persistent errs slice on the
struct which is appended across Build() calls; change validation to use a local
errs variable inside the Build() method (remove reliance on the builder's errs
field) so each Build() starts fresh. Locate ScheduledSessionBuilder and its
Build() method and: 1) make errs a local []error in Build(); 2) update any
validation helpers called by Build() (or their call sites) to return errors (or
append to the local errs) instead of mutating b.errs; and 3) keep the resource
population logic (resource field) as-is but only return accumulated local errs
from Build() so prior failures don't persist across builds.

Comment on lines +90 to +104
func (b *ScheduledSessionBuilder) Build() (*ScheduledSession, error) {
if b.resource.Name == "" {
b.errs = append(b.errs, fmt.Errorf("name is required"))
}
if b.resource.AgentID == "" {
b.errs = append(b.errs, fmt.Errorf("agent_id is required"))
}
if b.resource.Schedule == "" {
b.errs = append(b.errs, fmt.Errorf("schedule is required"))
}
if len(b.errs) > 0 {
return nil, fmt.Errorf("validation failed: %w", errors.Join(b.errs...))
}
return &b.resource, nil
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Build() exposes mutable internal builder state

Both builders return pointers to internal fields (&b.resource, &b.patch). If the same builder is reused, later setter calls can mutate previously built objects (Lines 103 and 146). Return a copy instead.

Suggested fix
 func (b *ScheduledSessionBuilder) Build() (*ScheduledSession, error) {
@@
-	return &b.resource, nil
+	out := b.resource
+	return &out, nil
 }
@@
 func (b *ScheduledSessionPatchBuilder) Build() *ScheduledSessionPatch {
-	return &b.patch
+	out := b.patch
+	return &out
 }

As per coding guidelines, **/*: - Prioritize Critical and Major severity issues. Minimize Minor and Trivial findings.

Also applies to: 145-147

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-sdk/go-sdk/types/scheduled_session.go` around lines 90 -
104, The Build methods are returning pointers to internal mutable builder fields
(e.g., ScheduledSessionBuilder.Build returning &b.resource), allowing later
builder calls to mutate previously built objects; change Build (and the
analogous builder method that returns &b.patch) to return a pointer to a copy
instead of the internal field by creating a local copy of b.resource (or
b.patch) and returning its address so the builder's internal state remains
immutable to callers.

@mergify mergify Bot added the queued label Apr 29, 2026
@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented Apr 29, 2026

Merge Queue Status

  • Entered queue2026-04-29 16:55 UTC · Rule: default
  • Checks skipped · PR is already up-to-date
  • Merged2026-04-29 16:55 UTC · at 1c2c067b2ce2fd9816d53ebaf8a7b2b1a71f23cb · squash

This pull request spent 13 seconds in the queue, including 1 second running CI.

Required conditions to merge

@mergify mergify Bot merged commit 7ba24ef into main Apr 29, 2026
64 of 69 checks passed
@mergify mergify Bot deleted the fix/google-oauth-redirect-uri-localhost branch April 29, 2026 16:55
@mergify mergify Bot removed the queued label Apr 29, 2026
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ SDD Preflight — Managed Paths Modified

This PR modifies files in SDD-managed component(s). These components are migrating to Spec-Driven Development.

File Component Mode
components/runners/ambient-runner/.mcp.json runner warn

No action required — these components are in warn mode. Consider using the component's agent workflow for future changes.

📖 Specs: Runner Spec · Runner Constitution

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