Skip to content

fix(oauth): honor MCPORTER_OAUTH_NO_BROWSER outside the auth command - #284

Merged
steipete merged 2 commits into
openclaw:mainfrom
vitalijssilins:fix/env-no-browser-serve-path
Aug 8, 2026
Merged

fix(oauth): honor MCPORTER_OAUTH_NO_BROWSER outside the auth command#284
steipete merged 2 commits into
openclaw:mainfrom
vitalijssilins:fix/env-no-browser-serve-path

Conversation

@vitalijssilins

@vitalijssilins vitalijssilins commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #283.

What

MCPORTER_OAUTH_NO_BROWSER was parsed only inside the auth command's argv handling (consumeBrowserSuppression), so the serve/daemon path still launched the system browser on every 401 — a focus-stealing, context-free tab when a token expires under an MCP bridge, and a tab loop when auth keeps failing (details in the issue).

How

  • redirectToAuthorization now falls back to the env var when no explicit suppressBrowserLaunch option was passed: this.options.suppressBrowserLaunch ?? suppressBrowserLaunchFromEnv(). An explicit option always wins, and the accepted values (1/true/yes, case-insensitive) mirror the auth command's TRUE_VALUES.
  • When suppression is active but no onAuthorizationUrl callback is registered (the headless serve/daemon case), the authorization URL is logged at warn level and the pending authorization is rejected with a server-named BrowserLaunchSuppressedError — callers fail fast with an actionable message (run 'mcporter auth <name> --no-browser') instead of silently waiting out the authorization timeout (the "remaining risk" flagged in serve/daemon path launches the system browser on 401 with no way to suppress it (MCPORTER_OAUTH_NO_BROWSER is auth-command-only) #283's review).
  • The rejection path is deliberate: the SDK invokes redirectToAuthorization from unawaited send paths, where a throw becomes an unhandled rejection that kills the daemon (we hit exactly that in a real run while iterating).
  • Existing callers that pass suppressBrowserLaunch: true with a callback (the auth command) are unaffected.

Real behavior proof (daemon, real 401)

Isolated $HOME with an empty vault (guaranteed 401), one keep-alive OAuth server (Supabase's public MCP endpoint), foreground daemon built from this branch, real callTool through the daemon client:

$ HOME=$PROOF MCPORTER_OAUTH_NO_BROWSER=1 node dist/cli.js daemon start --foreground --log
[daemon] 2026-08-07T08:41:41.627Z Daemon host started.
[daemon] 2026-08-07T08:41:45.606Z callTool start server=supabase-room tool=list_tables
[mcporter] Authorization required for supabase-room; browser launch suppressed (MCPORTER_OAUTH_NO_BROWSER). Visit https://api.supabase.com/v1/oauth/authorize?response_type=code&client_id=cf4aec55-…&code_challenge=…&redirect_uri=http%3A%2F%2F127.0.0.1%3A62199%2Fcallback&… to authorize.
[mcporter] OAuth authorization required for 'supabase-room'. Waiting for browser approval...
[mcporter] OAuth authorization failed while waiting for callback.
BrowserLaunchSuppressedError: Authorization required for 'supabase-room' but browser launch is suppressed (MCPORTER_OAUTH_NO_BROWSER); run 'mcporter auth supabase-room --no-browser' to authorize.
[daemon] 2026-08-07T08:41:47.396Z callTool error server=supabase-room tool=list_tables err=Authorization required for 'supabase-room' but browser launch is suppressed (…)
[daemon] 2026-08-07T08:41:47.399Z closeServer success server=supabase-room     ← daemon healthy, handles the error
[daemon] 2026-08-07T08:41:48.341Z Shutting down daemon host.                   ← clean shutdown at end of capture

$ time HOME=$PROOF node dist/cli.js call supabase-room list_tables
[mcporter] Authorization required for 'supabase-room' but browser launch is suppressed (MCPORTER_OAUTH_NO_BROWSER); run 'mcporter auth supabase-room --no-browser' to authorize.
call exit: 1
2.828 total    ← fails in ~3s with the actionable error, not the 300s authorization timeout

No browser launched at any point (no open spawn; nothing hit the OS). On current main the same run logs Authorization required for supabase-room. Opening browser... and launches the system browser from the daemon.

(The client_id in the transcript is a throwaway Dynamic Client Registration from the isolated home, since deleted.)

Tests

  • Env var set, no session options → openExternal not called, URL surfaced at warn level, late-attaching waiter rejects with BrowserLaunchSuppressedError (server-named).
  • Falsy value (0) → browser opens as before.
  • Explicit suppressBrowserLaunch: true + callback (auth command path) → unchanged, existing test still green.
  • pnpm check and full pnpm test green (1283 passed).

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. labels Aug 7, 2026
@clawsweeper

clawsweeper Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed August 8, 2026, 5:34 PM ET / 21:34 UTC.

ClawSweeper review

What this changes

The PR applies MCPORTER_OAUTH_NO_BROWSER to daemon OAuth flows, returns a non-retryable authorization failure, and refreshes daemons when that setting changes.

Merge readiness

⚠️ Ready for maintainer review - 2 items remain

Keep this PR open: current main still launches the browser in this daemon path, and the latest maintainer-authored follow-up addresses the prior replay concern with a stable OAuth error code and focused boundary coverage.

Priority: P2
Reviewed head: b7e29fa45425f8471cf7aca0d192e81f603712e9

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) The patch is focused, resolves the prior daemon replay finding, and has strong real behavior proof.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (terminal): The PR body contains an after-fix daemon run against a real OAuth server showing no browser launch and an actionable fast failure.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (terminal): The PR body contains an after-fix daemon run against a real OAuth server showing no browser launch and an actionable fast failure.
Evidence reviewed 5 items Current-main behavior: The current provider only suppresses a browser when its session option is explicitly set; otherwise it calls the external browser launcher, which establishes the reported gap.
Daemon boundary: Current daemon responses preserve a string error code on reconstructed client errors, enabling the PR’s new OAuth-specific code to reach the wrapper.
Prior finding addressed: The current PR-head commit adds the stable OAuth-flow code, host serialization, and wrapper non-retry path described in the supplied diff, with focused host, client, and wrapper tests.
Findings None None.
Security None None.

How this fits together

MCPorter’s daemon handles long-lived MCP tool calls and creates OAuth sessions when a remote server requires authorization. This change controls whether that session may launch a browser and how its failure is returned to the caller.

flowchart LR
  A[MCP tool request] --> B[Daemon client]
  B --> C[Daemon host]
  C --> D[OAuth session]
  D --> E{Browser suppressed?}
  E -->|No| F[Launch authorization browser]
  E -->|Yes| G[OAuth flow error]
  G --> B
  B --> H[Fail call without replay]
Loading

Before merge

  • Resolve merge risk (P1) - The merge-sensitive surface is OAuth error propagation across host, client, and wrapper; the current-head focused tests cover each boundary. The local partial clone could not load PR trees from its unavailable promisor remote, so the head assessment uses the supplied GitHub diff plus available commit metadata and current-main source.
  • Complete next step (P2) - No discrete repair remains after the latest head commit; this is ready for normal maintainer merge review.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Implementation and regression coverage source +75/-26; five test files +239 Most of the change is focused test coverage for OAuth, daemon protocol, freshness, and replay behavior.

Root-cause cluster

Relationship: fixed_by_candidate
Canonical: #283
Summary: This PR is the direct candidate fix for the daemon OAuth browser-launch behavior.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge-risk options

Maintainer options:

  1. Keep the stable OAuth error contract (recommended)
    Merge with the host serialization, client reconstruction, and wrapper non-retry tests intact so suppressed authorization cannot replay the daemon call.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Preserve the stable OAuth daemon error code and its focused host, client, and runtime-wrapper regression coverage.

Technical review

Best possible solution:

Merge the existing environment-variable path with its stable daemon error contract so headless callers receive one actionable authorization failure while interactive auth remains unchanged.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main source shows the browser launcher is used unless an explicit session option is supplied, and the PR provides a real daemon OAuth-401 transcript for the affected path.

Is this the best way to solve the issue?

Yes. Reusing the established environment variable and preserving a non-retryable error across the existing daemon boundary is narrower than adding a new setting or separate auth mode.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against c2747e2570c4.

Labels

Label changes:

  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body contains an after-fix daemon run against a real OAuth server showing no browser launch and an actionable fast failure.
  • remove status: ⏳ waiting on author: Current PR status label is status: 👀 ready for maintainer look.
  • remove rating: 🦐 gold shrimp: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.

Label justifications:

  • P2: This fixes disruptive browser launches during normal daemon-backed OAuth failures.
  • merge-risk: 🚨 auth-provider: The PR changes OAuth authorization failure handling across the daemon protocol.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body contains an after-fix daemon run against a real OAuth server showing no browser launch and an actionable fast failure.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body contains an after-fix daemon run against a real OAuth server showing no browser launch and an actionable fast failure.

Evidence

What I checked:

  • Current-main behavior: The current provider only suppresses a browser when its session option is explicitly set; otherwise it calls the external browser launcher, which establishes the reported gap. (src/oauth.ts:343, c2747e2570c4)
  • Daemon boundary: Current daemon responses preserve a string error code on reconstructed client errors, enabling the PR’s new OAuth-specific code to reach the wrapper. (src/daemon/client.ts:355, c2747e2570c4)
  • Prior finding addressed: The current PR-head commit adds the stable OAuth-flow code, host serialization, and wrapper non-retry path described in the supplied diff, with focused host, client, and wrapper tests. (src/daemon/runtime-wrapper.ts:180, b7e29fa45425)
  • Branch provenance: The PR head consists of the OAuth implementation followed by Peter Steinberger’s daemon-error preservation commit; current main is an ancestor, so the change is not already implemented on main. (src/daemon/host.ts:599, b7e29fa45425)
  • Real behavior proof: The PR body includes a redacted real daemon run against an OAuth server that shows browser suppression and a fast actionable failure rather than the authorization timeout. (b7e29fa45425)

Likely related people:

  • steipete: Peter Steinberger introduced the current OAuth browser-launch branch, has the strongest daemon-area history, and authored the PR-head follow-up that preserves the failure through daemon recovery. (role: original behavior author and recent area contributor; confidence: high; commits: 49dcd3e7fffd, b7e29fa45425; files: src/oauth.ts, src/daemon/host.ts, src/daemon/runtime-wrapper.ts)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (17 earlier review cycles; latest 8 shown)
  • reviewed 2026-08-08T04:41:13.901Z sha fbc594d :: needs changes before merge. :: [P2] Preserve suppression failure through daemon retries
  • reviewed 2026-08-08T05:43:58.712Z sha fbc594d :: needs changes before merge. :: [P2] Preserve suppressed-auth failures across daemon retries
  • reviewed 2026-08-08T11:51:41.484Z sha fbc594d :: needs changes before merge. :: [P2] Preserve suppressed-auth failures across daemon retries
  • reviewed 2026-08-08T14:05:26.987Z sha fbc594d :: needs changes before merge. :: [P2] Preserve suppressed-auth failures across daemon retries
  • reviewed 2026-08-08T16:54:42.570Z sha fbc594d :: needs changes before merge. :: [P2] Preserve suppressed-auth failures across daemon retries
  • reviewed 2026-08-08T19:03:23.653Z sha fbc594d :: needs changes before merge. :: [P2] Keep suppressed-auth failures out of daemon replay
  • reviewed 2026-08-08T20:14:55.568Z sha fbc594d :: needs changes before merge. :: [P2] Preserve suppressed-auth failures through daemon recovery
  • reviewed 2026-08-08T21:27:06.484Z sha fbc594d :: needs changes before merge. :: [P2] Preserve suppressed-auth failures through daemon recovery

@vitalijssilins
vitalijssilins force-pushed the fix/env-no-browser-serve-path branch from 0555305 to fbc594d Compare August 7, 2026 08:42
@vitalijssilins

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review — added real daemon-run proof (real 401 against a live OAuth server from an isolated home; transcript in the PR body) and hardened the headless path to fail fast with a server-named error instead of waiting out the authorization timeout, per the risk flagged in #283.

@clawsweeper

clawsweeper Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: when the review finishes, ClawSweeper will create the durable review comment if needed or update the existing comment in place.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 7, 2026
@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Aug 7, 2026
vitalijssilins and others added 2 commits August 8, 2026 14:21
…penclaw#283)

The env var was parsed only in the auth command's argv handling, so the
serve/daemon path still launched the system browser on every 401. Read it
as a fallback in redirectToAuthorization (an explicit suppressBrowserLaunch
option still wins). When no onAuthorizationUrl callback is registered (the
headless serve/daemon case), log the authorization URL at warn level and
reject the pending authorization with a server-named
BrowserLaunchSuppressedError so callers fail fast instead of waiting out
the authorization timeout. The rejection path is used deliberately: the
SDK invokes redirectToAuthorization from unawaited send paths, where a
throw becomes an unhandled rejection that kills the daemon.
Co-authored-by: Vitālijs Silins-Ozers <silins@silins.lv>
@steipete
steipete force-pushed the fix/env-no-browser-serve-path branch from fbc594d to b7e29fa Compare August 8, 2026 21:30
@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Aug 8, 2026
@clawsweeper clawsweeper Bot added the status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. label Aug 8, 2026
@steipete

steipete commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Exact-head maintainer proof for b7e29fa45425f8471cf7aca0d192e81f603712e9:

  • Rebased onto current main and retained Vitālijs Silins-Ozers / @vitalijssilins as author of the contributor commit. The maintainer follow-up carries his co-author trailer and adds the 0.13.1 changelog credit.
  • Root cause/provenance: PR feat: add headless OAuth browser suppression #171 added provider/session no-browser support, but MCPORTER_OAUTH_NO_BROWSER normalization remained inside the auth command. Runtime/serve/daemon sessions therefore launched the browser. The new suppressed error then needed to preserve the existing Serialize interactive OAuth authorization per shared provider/session #247 no-replay daemon policy instead of becoming generic runtime_error.
  • Provider behavior: ambient no-browser suppression now applies at the provider boundary; explicit options override env; 1 / true / yes normalize truthy while false/unknown values preserve browser behavior; suppression rejects the handled deferred promptly with server-named guidance and does not log the authorization URL.
  • Daemon ownership: OAuth-flow failures serialize/reconstruct as stable oauth_flow_error; KeepAliveRuntime returns that code without closeServer or replay; optional oauthNoBrowser freshness metadata normalizes missing to false and replaces a daemon when the effective env setting changes. No protocol-version bump or per-request config contract was added.
  • Diff: 14 files, +317/-27. Production +75/-26, tests +239/-1, docs/changelog +3. Risk is concentrated in OAuth daemon freshness/error routing; the wire additions are optional and backward compatible.
  • Focused proof: pnpm exec vitest run tests/oauth-session.test.ts tests/cli-auth.test.ts tests/daemon-host.test.ts tests/daemon-client-timeout.test.ts tests/daemon-client-config-stale.test.ts tests/daemon-client-lifecycle.test.ts tests/keep-alive-runtime.test.ts — 7 files, 99 tests passed.
  • Repository gates: pnpm docs:list, pnpm check, pnpm docs:site, pnpm test, and git diff --check — all clean. Full suite: 185 files passed / 4 skipped; 1,409 tests passed / 26 skipped.
  • Credential-free built-artifact live proof: an isolated foreground daemon started with normalized suppression false at PID 20572. A single built-CLI keep-alive call under MCPORTER_OAUTH_NO_BROWSER=1 replaced it with PID 23630 and metadata oauthNoBrowser: true; the local 401 OAuth/MCP server observed exactly one MCP request, one DCR request, and zero authorization requests. The call exited once with oauth_flow_error and mcporter auth proof-oauth --no-browser guidance, with no restart message, no replay, no authorization URL, and no closeServer. A fake open executable was first on PATH and its sentinel remained absent, proving no browser launch was attempted.
  • Structured Codex-backed autoreview: final integrated branch run clean with no accepted/actionable findings; secret scan clean.
  • Exact diff/public-proof model-identifier audit: PASS.
  • First-time-contributor CI was approved for this exact head. Run 31279498895 passed Ubuntu, macOS, and Windows. PR state is mergeable/CLEAN.

Gaps: the proof intentionally used synthetic local OAuth metadata/DCR and no credentials; this in-process browser-suppression, daemon freshness, and no-replay boundary does not require an external provider or real browser.

Thanks @vitalijssilins for the high-quality issue, core provider implementation, and real daemon investigation.

@steipete
steipete merged commit 490a350 into openclaw:main Aug 8, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

serve/daemon path launches the system browser on 401 with no way to suppress it (MCPORTER_OAUTH_NO_BROWSER is auth-command-only)

2 participants