Skip to content

fix(oauth): route assistant oauth connect --callback-transport=gateway through daemon IPC#29596

Merged
noanflaherty merged 9 commits into
mainfrom
credence-the-bot/oauth-connect-daemon-flow
May 5, 2026
Merged

fix(oauth): route assistant oauth connect --callback-transport=gateway through daemon IPC#29596
noanflaherty merged 9 commits into
mainfrom
credence-the-bot/oauth-connect-daemon-flow

Conversation

@credence-the-bot
Copy link
Copy Markdown
Contributor

@credence-the-bot credence-the-bot Bot commented May 5, 2026

Summary

Fixes the heap-split bug in assistant oauth connect <provider> --callback-transport=gateway where registerPendingCallback runs in the CLI process heap but consumeCallback runs in the daemon heap — so the callback is never found and the flow times out.

Root cause: The CLI was calling orchestrateOAuthConnect locally (in its own heap). The daemon's consumeCallback receives the gateway callback in its heap, but can't find the pending entry registered by the CLI.

Fix: Route the flow through the daemon via IPC:

  • New daemon routes POST internal/oauth/connect/start + GET internal/oauth/connect/status/:state orchestrate the OAuth flow entirely inside the daemon
  • CLI becomes a thin IPC shim: it calls /start, opens the browser, then polls /status/:state until completion
  • Falls back to the original in-process path if the daemon is unreachable (backward compat)

Changes

  • assistant/src/oauth/oauth-connect-state.ts — new in-memory state map (keyed by state token UUID) with TTL cleanup
  • assistant/src/runtime/routes/oauth-connect-routes.ts — two new daemon routes
  • assistant/src/runtime/routes/index.ts — register new routes
  • assistant/src/runtime/auth/route-policy.ts — add svc_gateway-only policy for new internal endpoints
  • assistant/src/cli/commands/oauth/connect.ts — IPC-first path with poll helper; falls back to in-process when IPC unavailable
  • assistant/src/oauth/connect-orchestrator.ts — thread grantedScopes through onDeferredComplete callback
  • Tests for all of the above

Test plan

  • Unit tests for new state module (TTL expiry, all transitions)
  • Unit tests for new daemon routes (happy path, errors, already-authenticated)
  • Unit tests for route policy (svc_gateway allowed, others denied)
  • Unit tests for CLI IPC path (happy path, transient failure retry, daemon error surfaced, fallback)
  • Integration: assistant oauth connect <provider> --callback-transport=gateway completes without heap-split timeout

🤖 Generated with Claude Code


Open in Devin Review

credence-the-bot Bot and others added 4 commits May 5, 2026 04:25
…ay` through daemon IPC (#29570)

* fix(oauth): route assistant oauth connect --callback-transport=gateway through daemon IPC

* fix(oauth-connect): pass lint, type-check, and openapi-check

- Sort imports per simple-import-sort/imports rule (autofix)
- Add eslint-disable for the intentionally-let resolvedState binding
- Narrow OAuthConnectResult union before reading .error (split into two if branches)
- Narrow OAuthConnectStatusResponse before reading .error in CLI poll loop
- Regenerate openapi.yaml to include the 2 new internal routes

* fix: use operationId and structured params for cliIpcCall (Devin/Codex P1 review)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: thread grantedScopes through IPC path to avoid empty-scopes inconsistency (Devin review)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(oauth): align IPC poll timeout with 5-min loopback OAuth window (Codex review)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: credence-the-bot <credence@vellum.ai>
Co-authored-by: credence-the-bot[bot] <183148327+credence-the-bot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ansport test and policy tests (#29590)

Co-authored-by: credence-the-bot <credence@vellum.ai>
…auth_url (#29593)

Co-authored-by: credence-the-bot <credence@vellum.ai>
…3) (#29595)

Co-authored-by: credence-the-bot <credence@vellum.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c10f6e5c50

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if (opts.browser !== false) {
await openInHostBrowser(auth_url);

log.info("Waiting for authorization in browser... (press Ctrl+C to cancel)");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Suppress progress logs when emitting JSON OAuth connect output

When assistant oauth connect ... --json succeeds via the new IPC path, this unconditional log.info(...) writes a plain-text line to stdout before the JSON payload. Machine consumers that parse stdout as JSON (including scripts relying on a single JSON object) will fail even though the command succeeded. The managed path already guards informational logs behind !jsonMode, so this BYO IPC path should do the same to keep JSON mode output parseable.

Useful? React with 👍 / 👎.

Comment on lines +53 to +55
export function getOAuthConnectState(state: string): OAuthConnectState | null {
clearExpiredOAuthConnectStates();
return activeOAuthConnectFlows.get(state) ?? null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Expire OAuth flow state without requiring status polling

State eviction only runs inside getOAuthConnectState(), so flows that are started but never polled (for example --no-browser usage, abandoned flows, or callers that stop polling) leave entries in activeOAuthConnectFlows indefinitely. Over time, repeated starts can accumulate stale in-memory state in the daemon process. Expiration should be driven independently (e.g., timer/sweeper or cleanup on writes) rather than depending on future status reads.

Useful? React with 👍 / 👎.

devin-ai-integration[bot]

This comment was marked as resolved.

…e on insertion (#29597)

* fix: suppress browser-wait log in JSON mode; sweep expired state on insert

* test: make JSON-mode log suppression test exercise the actual if (!jsonMode) guard

Configure the logger mock to write to process.stdout in the specific test so
the test would fail if the guard were removed from connect.ts. Also reset
mockLogInfo to no-op in beforeEach for test isolation.

---------

Co-authored-by: credence-the-bot <credence@vellum.ai>
…rings (#29603)

* fix(oauth): replace 'Daemon' with 'assistant' in user-facing error strings

* test: update assertion to match new 'assistant' string from devin nit fix

The previous commit replaced 'Daemon' with 'assistant' in user-facing
error strings (per Devin review), but missed updating the test that
asserts on the old 'Daemon returned unexpected response' wording.

---------

Co-authored-by: credence-the-bot <credence@vellum.ai>
Co-authored-by: credence-the-bot[bot] <credence-the-bot[bot]@users.noreply.github.com>
@credence-the-bot
Copy link
Copy Markdown
Contributor Author

@codex review this PR again — all previously-flagged issues (P1 IPC operationId/params, P2 daemon JSON parse, error string consistency, expired OAuth state sweep) have been addressed in HEAD 0f4e672d1f6546ac3a3db638388b05bfd5bd39e5. CI is fully green.

@credence-the-bot
Copy link
Copy Markdown
Contributor Author

@devin review this PR again — all previously-flagged issues (P1 IPC operationId/params, P2 daemon JSON parse, error string consistency, expired OAuth state sweep, 'Daemon' → 'assistant' user-facing strings) have been addressed in HEAD 0f4e672d1f6546ac3a3db638388b05bfd5bd39e5. CI is fully green.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f4e672d1f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


export const ROUTES: RouteDefinition[] = [
{
operationId: "internal_oauth_connect_start",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add IPC proxy policy for new internal OAuth operationIds

Registering internal_oauth_connect_start/internal_oauth_connect_status in the shared ROUTES array exposes them to the gateway IPC proxy, but gateway/src/auth/ipc-route-policy.ts has no entries for these operationIds. In tryIpcProxy() (gateway/src/http/routes/ipc-runtime-proxy.ts), missing policy means getIpcRoutePolicy() returns undefined and the request is allowed once JWT auth passes, so an actor token can call /v1/internal/oauth/connect/* by setting X-Vellum-Proxy-Server: ipc, bypassing the daemon-side svc_gateway restriction intended in route-policy.ts.

Useful? React with 👍 / 👎.

Brings in 18 commits since this branch was cut, including:
- #29571 (security): IPC policy entries for daemon MCP OAuth routes
- Memory v2 work, proactive-artifact, trace-events index, salesforce OAuth, etc.

3-way merge clean (only auto-merged assistant/openapi.yaml).
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 11 additional findings in Devin Review.

Open in Devin Review

Comment on lines +126 to +145
summary: "Start daemon-owned OAuth connect flow",
description:
"Starts an OAuth connect flow in the daemon and returns the authorization URL for the CLI to open in the browser.",
tags: ["internal"],
requestBody: z.object({
service: z.string(),
clientId: z.string(),
clientSecret: z.string().optional(),
callbackTransport: z.enum(["loopback", "gateway"]),
requestedScopes: z.array(z.string()).optional(),
}),
handler: handleOAuthConnectStart,
},
{
operationId: "internal_oauth_connect_status",
endpoint: "internal/oauth/connect/status/:state",
method: "GET",
summary: "Poll daemon OAuth connect flow status",
description:
"Returns the current status of an in-flight daemon-owned OAuth connect flow (pending/complete/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.

🚩 Route summary/description use 'daemon' terminology in internal-only routes

The route summary and description fields (lines 126-128, 143-145) use 'daemon-owned' and 'in the daemon'. Per the AGENTS.md user-facing terminology rule, 'daemon' should be 'assistant' in user-facing text. However, these are internal-tagged routes accessible only by svc_gateway principals, and their descriptions appear only in the auto-generated OpenAPI spec. The existing MCP auth routes (openapi.yaml:6724) already use identical 'daemon-owned' phrasing in the same context. This follows the established convention for internal API documentation that no end-user sees, though it's a gray area — the OpenAPI spec could be considered developer documentation.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

…#29612)

Closes Codex P1 on #29596 by adding IPC policy entries for the new daemon OAuth connect routes (`internal_oauth_connect_start`, `internal_oauth_connect_status`). Mirrors the pattern from #29571 for the MCP OAuth routes. 4-line additive change, 6/6 tests pass. Both Codex and Devin re-reviewed clean.
@credence-the-bot
Copy link
Copy Markdown
Contributor Author

@chatgpt-codex-connector review

The P1 you flagged on the previous HEAD (0f4e672d1f) — IPC policy entries missing for the new daemon OAuth connect routes — has been addressed via sub-PR #29612 which merged into this feature branch as commit 740326529c.

Summary of what changed since your last review:

  1. Codex P1 closed: Added internal_oauth_connect_start and internal_oauth_connect_status entries to gateway/src/auth/ipc-route-policy.ts POLICY_TABLE with internal.write scope and svc_gateway principal restriction. Mirrors the pattern from fix(security): add IPC policy entries for daemon MCP OAuth routes #29571 for the MCP OAuth routes. gateway/src/__tests__/ipc-route-policy.test.ts extended to cover both new operationIds (6/6 tests passing).
  2. Branch sync: Merged main into feature branch — absorbs fix(security): add IPC policy entries for daemon MCP OAuth routes #29571 (Noa's MCP IPC policy fix) plus 17 other commits since merge-base. 3-way merge clean (only auto-merged assistant/openapi.yaml).

Current HEAD: 740326529c. Ready for your final pass before Noa merges.

@devin-ai-integration please re-review as well — feature branch has been updated.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@credence-the-bot
Copy link
Copy Markdown
Contributor Author

@devin-ai-integration review this PR again — the IPC-policy P1 you flagged on a400550be has been closed via sub-PR #29612 which has now merged into the feature branch (HEAD 740326529c). Codex re-reviewed and reported no major issues. Asking for a fresh pass to confirm the IPC policy entries (internal_oauth_connect_start, internal_oauth_connect_status) match the daemon-side route policy.

@credence-the-bot
Copy link
Copy Markdown
Contributor Author

Status update for the morning review (HEAD 740326529c):

Resolved on this HEAD:

Gray-area Devin nit on this HEAD (740326529c):

  • "Route summary/description use daemon-owned terminology in internal-only routes" (oauth-connect-routes.ts:126-145). Devin themselves note this follows the established convention for the MCP auth routes (openapi.yaml:6724) and these routes are svc_gateway-only internal endpoints. Deferring to keep terminology consistent with the existing internal API documentation; reopening as a sweep across both surfaces would be a separate non-functional PR if we want to standardize.

@chatgpt-codex-connector review

Re-tagging Codex for a fresh pass on 740326529c since their last review was on 0f4e672d1f (before the IPC policy fix landed via #29612).

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

…h-connect-daemon-flow

# Conflicts:
#	assistant/src/runtime/auth/route-policy.ts
@credence-the-bot
Copy link
Copy Markdown
Contributor Author

@chatgpt-codex-connector review

Merged main into this branch (HEAD 545e87993f) to absorb the memory/v2 work that landed overnight (PRs #29622, #29625, #29626, #29631 etc) and resolve the merge conflict in assistant/src/runtime/auth/route-policy.ts. Re-review the new HEAD please.

Substantive change vs. previously-clean HEAD 740326529c:

  • Added origin/main as a parent (no behavioral change).
  • Resolved route-policy.ts INTERNAL_ENDPOINTS conflict: kept main's whitespace style and appended my two new entries (internal/oauth/connect/start, internal/oauth/connect/status).
  • Auto-merge resolved assistant/openapi.yaml cleanly — verified both sets of additions present (oauth/connect/{start,status} + memory/v2/concept-frequency).

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@noanflaherty noanflaherty merged commit 6807b40 into main May 5, 2026
14 checks passed
@noanflaherty noanflaherty deleted the credence-the-bot/oauth-connect-daemon-flow branch May 5, 2026 13:22
credence-the-bot Bot pushed a commit that referenced this pull request May 6, 2026
…onnect`

The CLI's BYO OAuth connect path used to fall through to an in-process
`orchestrateOAuthConnect` invocation when the daemon IPC was
unreachable. That fallback was buggy and dead code in practice:

  - It re-introduced the heap-split bug for `--callback-transport=gateway`
    (platform redirect lands in a different process from the loopback
    waiter — see #29596).
  - The OAuth tokens it would acquire need the daemon running before
    they're usable, so "daemon unreachable" is already a fatal
    precondition for the user's flow.

Now we surface a clear "Is the assistant running?" error and exit 1
when the daemon is unreachable, matching the MCP CLI consolidation in
#29484. The daemon-orchestrated IPC path remains the canonical
implementation; nothing changes when the daemon is up.
noanflaherty pushed a commit that referenced this pull request May 6, 2026
…onnect` CLI (#29756)

* fix(oauth): drop in-process loopback fallback from `assistant oauth connect`

The CLI's BYO OAuth connect path used to fall through to an in-process
`orchestrateOAuthConnect` invocation when the daemon IPC was
unreachable. That fallback was buggy and dead code in practice:

  - It re-introduced the heap-split bug for `--callback-transport=gateway`
    (platform redirect lands in a different process from the loopback
    waiter — see #29596).
  - The OAuth tokens it would acquire need the daemon running before
    they're usable, so "daemon unreachable" is already a fatal
    precondition for the user's flow.

Now we surface a clear "Is the assistant running?" error and exit 1
when the daemon is unreachable, matching the MCP CLI consolidation in
#29484. The daemon-orchestrated IPC path remains the canonical
implementation; nothing changes when the daemon is up.

* test(oauth-cli): add daemon-unreachable regression coverage for BYO connect

Three new tests in `oauth-cli.test.ts` lock in the post-cleanup invariant
that the BYO `oauth connect` flow MUST exit 1 when the daemon is
unreachable, never silently fall back to in-process orchestration:

  1. ECONNREFUSED → exit 1, error mentions 'Is the assistant running?'
  2. 'Unknown method' → exit 1; `orchestrateOAuthConnect` is NOT called
  3. Daemon HTTP error (statusCode set) → surfaces error verbatim

Tests (2) and (3) explicitly count invocations of the in-process
orchestrator mock and assert zero calls — this is the same regression
guard pattern PR #29484 used for the MCP CLI consolidation.

* fix(oauth-cli): address review feedback

Address Codex P1s + Devin findings on PR #29756:

1. **Use 'assistant' instead of 'daemon' in user-facing CLI errors.**
   Per assistant/AGENTS.md, 'daemon' is an internal implementation
   detail and CLI output must use 'assistant' for user-facing language.
   The new error string in connect.ts violated this.

2. **Drop historical-narrative comment block.** AGENTS.md prohibits
   comments that reference removed code or prior implementations
   ('We previously...'). Replaced the block with a description of
   current behavior only — why an unreachable assistant is a fatal
   precondition, with no reference to the removed loopback fallback
   or PR history.

3. **Delete 6 tests that pinned the removed in-process fallback.**
   These tests mocked `mockOrchestrateOAuthConnect` and asserted
   the orchestrator was called or its result surfaced. With the
   loopback fallback removed, every CLI path now goes through IPC,
   so those tests assert removed behavior. The IPC-first path tests
   (added in the PR's first commit) cover every JSON shape and
   error case the deleted tests asserted on:
     - 'BYO mode --no-browser prints auth URL' →
       'IPC start + --no-browser without json → prints URL'
     - 'BYO default calls orchestrator with isInteractive: true' →
       deleted; orchestrator no longer in CLI heap
     - 'JSON output for deferred case' →
       'IPC start + --no-browser + json → returns deferred JSON'
     - 'JSON output for completed case' →
       'IPC start succeeds + polling returns complete'
     - 'IPC ok:false → falls back to in-process orchestrator' →
       opposite is now true; covered by 'IPC ok:false with statusCode
       → does NOT fall back'
     - 'orchestrator error propagates correctly' →
       'IPC poll returns ok:false with statusCode → breaks early'

   18 tests pass (down from 18 surviving + 6 broken = 24 total).

* fix(test): update oauth-cli.test.ts assertion to new error wording

Sibling test file at assistant/src/__tests__/oauth-cli.test.ts:1288
still asserted on the previous 'Could not reach the assistant daemon'
string. Updated to match the new wording in connect.ts (per
assistant/AGENTS.md: 'daemon' is internal implementation detail).

---------

Co-authored-by: credence-the-bot[bot] <207016121+credence-the-bot[bot]@users.noreply.github.com>
Co-authored-by: credence-the-bot[bot] <credence-the-bot[bot]@users.noreply.github.com>
siddseethepalli added a commit that referenced this pull request May 9, 2026
…30126)

Bookmarks routes (#30119/#30122) added new endpoints under /v1/bookmarks
but assistant/openapi.yaml was not regenerated, causing the OpenAPI Spec
Check job on main to fail.

Also adds a SAFE_LINE_PATTERN to the secrets pre-commit hook for YAML
schema property declarations (e.g. `clientSecret:` followed by a nested
`type: string` on the next line), which exists in the spec since #29596
but was previously unrecognized by the hook.

Co-authored-by: Vellum Assistant <assistant@vellum.ai>
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