From 925d65a499cc30ae051f568805b2ec651ffe56f3 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:29:14 +0000 Subject: [PATCH] fix: Python 3.14 compatibility for OAuth device flow Fixes weak reference error in asyncio.run() on Python 3.14.2. Python 3.14 introduced stricter event loop lifecycle management that causes weak reference errors when synchronous code (webbrowser.open) is called from within async contexts managed by asyncio.run(). Changes: - Run webbrowser.open() in ThreadPoolExecutor to isolate from event loop - Make display_user_instructions() async and await it in login flow - Adds Python 3.14 compatibility while maintaining backward compatibility Fixes #480 Co-authored-by: Paul Hernandez Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> --- src/basic_memory/cli/auth.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/basic_memory/cli/auth.py b/src/basic_memory/cli/auth.py index 7a746bf4..c4ea69e4 100644 --- a/src/basic_memory/cli/auth.py +++ b/src/basic_memory/cli/auth.py @@ -71,7 +71,7 @@ class CLIAuth: console.print(f"[red]Device authorization error: {e}[/red]") return None - def display_user_instructions(self, device_response: dict) -> None: + async def display_user_instructions(self, device_response: dict) -> None: """Display user instructions for device authorization.""" user_code = device_response["user_code"] verification_uri = device_response["verification_uri"] @@ -87,9 +87,15 @@ class CLIAuth: console.print(f"[bold green]{verification_uri_complete}[/bold green]") # Try to open browser automatically + # Python 3.14: Run webbrowser.open in executor to avoid weak reference issues try: console.print("\n[dim]Opening browser automatically...[/dim]") - webbrowser.open(verification_uri_complete) + import asyncio + import concurrent.futures + + loop = asyncio.get_event_loop() + with concurrent.futures.ThreadPoolExecutor() as executor: + await loop.run_in_executor(executor, webbrowser.open, verification_uri_complete) except Exception: pass # Silently fail if browser can't be opened @@ -252,7 +258,7 @@ class CLIAuth: return False # Step 2: Display user instructions - self.display_user_instructions(device_response) + await self.display_user_instructions(device_response) # Step 3: Poll for token device_code = device_response["device_code"]