Skip to content

Commit c687571

Browse files
author
Claude Agent
committed
fix(auth): make browser session refresh language-agnostic
- Auto-dismiss cookie banners before clicking authorize - Find authorize button by background color (primary button style) instead of text - Skip deny buttons by checking common deny patterns across languages - Works with any language setting (German, English, French, etc.)
1 parent 03fd3a7 commit c687571

2 files changed

Lines changed: 85 additions & 12 deletions

File tree

packages/app/src/components/settings-providers.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,12 @@ function ProviderDetailView(props: { providerID: string; providerName: string; o
170170
if (result.ok) {
171171
await refetch()
172172
await loadBrowserSessions()
173+
} else {
174+
const error = await result.json().catch(() => ({ message: "Unknown error" }))
175+
alert(`Refresh failed: ${error.message || "Unknown error"}`)
173176
}
177+
} catch (e) {
178+
alert(`Refresh error: ${e}`)
174179
} finally {
175180
setRefreshingBrowser(null)
176181
}
@@ -185,7 +190,12 @@ function ProviderDetailView(props: { providerID: string; providerName: string; o
185190
if (result.ok) {
186191
await refetch()
187192
await loadBrowserSessions()
193+
} else {
194+
const error = await result.json().catch(() => ({ message: "Unknown error" }))
195+
alert(`Rebind failed: ${error.message || "Unknown error"}`)
188196
}
197+
} catch (e) {
198+
alert(`Rebind error: ${e}`)
189199
} finally {
190200
setRebindingBrowser(null)
191201
}

packages/opencode/src/auth/browser.ts

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -608,33 +608,87 @@ export namespace AuthBrowser {
608608
if (code) break
609609
}
610610

611-
// Try to click "Authorize" button if we're on consent screen
611+
// Try to dismiss cookie banner first, then click "Authorize" button (language-agnostic)
612612
if (!clickedAuthorize && currentUrl.includes("claude.ai")) {
613613
try {
614-
// Try multiple selectors for the authorize button
614+
// Try to dismiss cookie banner if present
615+
const dismissedCookie = await page.evaluate(() => {
616+
// Look for cookie-related containers
617+
const cookieSelectors = [
618+
'[data-testid*="cookie"]',
619+
'[class*="cookie"]',
620+
'[id*="cookie"]',
621+
'[class*="consent"]',
622+
'[id*="consent"]',
623+
]
624+
625+
for (const selector of cookieSelectors) {
626+
const container = document.querySelector(selector)
627+
if (container) {
628+
const buttons = Array.from(container.querySelectorAll("button"))
629+
if (buttons.length > 0) {
630+
// Click the last button (usually "Accept All")
631+
buttons[buttons.length - 1].click()
632+
return true
633+
}
634+
}
635+
}
636+
return false
637+
})
638+
if (dismissedCookie) {
639+
log.info("dismissed cookie banner")
640+
await new Promise((resolve) => setTimeout(resolve, 1000))
641+
}
642+
643+
// Try to find and click the authorize button (language-agnostic)
644+
// Strategy: Find the primary button (with solid background) that's not a deny button
615645
const clicked = await page.evaluate(() => {
616-
// Find button with text "Authorize"
617-
const buttons = Array.from(document.querySelectorAll("button"))
618-
for (const btn of buttons) {
619-
if (btn.textContent?.trim() === "Authorize" || btn.textContent?.includes("Authorize")) {
646+
const mainContent = document.querySelector("main") || document.body
647+
const buttons = Array.from(mainContent.querySelectorAll("button"))
648+
649+
// Filter visible, enabled buttons
650+
const visibleButtons = buttons.filter((btn) => {
651+
const style = getComputedStyle(btn)
652+
return (
653+
style.display !== "none" &&
654+
style.visibility !== "hidden" &&
655+
!btn.disabled &&
656+
btn.offsetParent !== null
657+
)
658+
})
659+
660+
// Deny patterns to skip (common words across languages)
661+
const denyPatterns = ["deny", "cancel", "reject", "decline", "no", "nein", "non", "nie"]
662+
663+
// Find primary button (with background color = filled/primary style)
664+
for (const btn of visibleButtons) {
665+
const bg = getComputedStyle(btn).backgroundColor
666+
const text = btn.textContent?.toLowerCase() || ""
667+
668+
if (denyPatterns.some((p) => text.includes(p))) continue
669+
670+
const hasBackground = bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent"
671+
if (hasBackground) {
620672
btn.click()
621673
return true
622674
}
623675
}
624-
// Also try input submit buttons
625-
const inputs = Array.from(document.querySelectorAll('input[type="submit"]'))
626-
for (const input of inputs) {
627-
if ((input as HTMLInputElement).value?.includes("Authorize")) {
628-
;(input as HTMLInputElement).click()
676+
677+
// Fallback: first non-deny button
678+
for (const btn of visibleButtons) {
679+
const text = btn.textContent?.toLowerCase() || ""
680+
if (!denyPatterns.some((p) => text.includes(p))) {
681+
btn.click()
629682
return true
630683
}
631684
}
685+
632686
return false
633687
})
634688
if (clicked) {
635689
log.info("clicked authorize button")
636690
clickedAuthorize = true
637-
await new Promise((resolve) => setTimeout(resolve, 2000)) // Wait for redirect
691+
await new Promise((resolve) => setTimeout(resolve, 2000))
638692
}
639693
} catch {
640694
// Button not found or click failed, continue polling
@@ -646,6 +700,15 @@ export namespace AuthBrowser {
646700
}
647701

648702
if (!code) {
703+
// Save debug screenshot for troubleshooting
704+
try {
705+
const screenshotPath = path.join(profilePath, "debug-screenshot.png")
706+
await page.screenshot({ path: screenshotPath, fullPage: true })
707+
log.info("saved debug screenshot", { screenshotPath })
708+
} catch {
709+
// Screenshot failed
710+
}
711+
649712
throw new Error("Session expired or refresh timed out. Please run setup again.")
650713
}
651714

0 commit comments

Comments
 (0)