From 6ef70823beb5727eeaffe8d0daa704b3f2a2ed80 Mon Sep 17 00:00:00 2001 From: Johannes du Plessis Date: Thu, 30 Apr 2026 17:48:15 -0700 Subject: [PATCH] fix(open_pr): reuse config for GitHub token; defer installation token lookup (#1227) Calling get_github_token() without arguments always invoked LangGraph get_config internally, which broke tests that only patch agent.middleware.open_pr.get_config and failed outside runnable context. Extend get_github_token with an optional runnable config mapping; the middleware passes the config dict already resolved from get_config(). Request GitHub App installation tokens only after detecting sandbox/repo changes worth publishing. Fixes failing Agent unit tests in tests/test_open_pr_middleware.py. --- agent/middleware/open_pr.py | 12 ++++++------ agent/utils/github_token.py | 14 ++++++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/agent/middleware/open_pr.py b/agent/middleware/open_pr.py index 249d6e1b..28c786ea 100644 --- a/agent/middleware/open_pr.py +++ b/agent/middleware/open_pr.py @@ -93,18 +93,13 @@ async def open_pr_if_needed( pr_title = pr_payload.get("title", "feat: Open SWE PR") pr_body = pr_payload.get("body", "Automated PR created by Open SWE agent.") commit_message = pr_payload.get("commit_message", pr_title) - github_token = get_github_token() + github_token = get_github_token(config) user_identity = await asyncio.to_thread( resolve_triggering_user_identity, config, github_token ) pr_body = add_pr_collaboration_note(pr_body, user_identity) commit_message = add_user_coauthor_trailer(commit_message, user_identity) - installation_token = await get_github_app_installation_token() - if not installation_token: - logger.error("Failed to get GitHub App installation token for thread %s", thread_id) - return None - if not thread_id: raise ValueError("No thread_id found in config") @@ -132,6 +127,11 @@ async def open_pr_if_needed( logger.info("No changes detected, skipping PR creation") return None + installation_token = await get_github_app_installation_token() + if not installation_token: + logger.error("Failed to get GitHub App installation token for thread %s", thread_id) + return None + logger.info("Changes detected, preparing PR for thread %s", thread_id) metadata = config.get("metadata", {}) diff --git a/agent/utils/github_token.py b/agent/utils/github_token.py index 7f85c176..afe5217e 100644 --- a/agent/utils/github_token.py +++ b/agent/utils/github_token.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging +from collections.abc import Mapping from typing import Any from langgraph.config import get_config @@ -30,10 +31,15 @@ def _decrypt_github_token(encrypted_token: str | None) -> str | None: return decrypt_token(encrypted_token) -def get_github_token() -> str | None: - """Resolve a GitHub token from run metadata.""" - config = get_config() - return _decrypt_github_token(_read_encrypted_github_token(config.get("metadata", {}))) +def get_github_token(run_config: Mapping[str, Any] | None = None) -> str | None: + """Resolve a GitHub token from run metadata. + + Pass ``run_config`` when LangGraph runnable config is already available (e.g. after + ``get_config()`` in callers). Omit to read from ``get_config()`` (required runnable + context). + """ + resolved = run_config if run_config is not None else get_config() + return _decrypt_github_token(_read_encrypted_github_token(resolved.get("metadata", {}))) async def get_github_token_from_thread(thread_id: str) -> tuple[str | None, str | None]: