From 7b1c9bfdca0140dea5de663b785647eb8d3a56a2 Mon Sep 17 00:00:00 2001 From: aran-yogesh Date: Fri, 13 Feb 2026 18:28:41 -0800 Subject: [PATCH 1/3] Fix clone error when repo directory already exists --- apps/agent/agent/server.py | 141 ++++++++++++++++++------------- apps/agent/agent/utils/github.py | 22 +++++ 2 files changed, 103 insertions(+), 60 deletions(-) diff --git a/apps/agent/agent/server.py b/apps/agent/agent/server.py index c44c76b4..e9a0c4ee 100644 --- a/apps/agent/agent/server.py +++ b/apps/agent/agent/server.py @@ -46,6 +46,12 @@ SANDBOX_CREATION_TIMEOUT = 180 SANDBOX_POLL_INTERVAL = 1.0 from .utils.sandbox_state import SANDBOX_BACKENDS +from .utils.github import ( + git_has_uncommitted_changes, + is_valid_git_repo, + remove_directory, + repo_directory_exists, +) async def _clone_or_pull_repo_in_sandbox( # noqa: PLR0915 @@ -76,44 +82,54 @@ async def _clone_or_pull_repo_in_sandbox( # noqa: PLR0915 repo_dir = f"/workspace/{repo}" - logger.debug("Checking if repo already exists at %s", repo_dir) - try: - check_result = await loop.run_in_executor( - None, sandbox_backend.execute, f"test -d {repo_dir}/.git && echo exists" - ) - logger.debug( - "Check result: exit_code=%s, output=%s", - check_result.exit_code, - check_result.output[:200] if check_result.output else "", - ) - except Exception: - logger.exception("Failed to execute check command in sandbox") - raise + # Check if directory exists + logger.debug("Checking if repo directory exists at %s", repo_dir) + dir_exists = await loop.run_in_executor(None, repo_directory_exists, sandbox_backend, repo_dir) - if check_result.exit_code == 0 and "exists" in check_result.output: - logger.info("Repo already exists at %s, pulling latest changes", repo_dir) - try: - status_result = await loop.run_in_executor( - None, sandbox_backend.execute, f"cd {repo_dir} && git status --porcelain" + if dir_exists: + is_git_repo = await loop.run_in_executor(None, is_valid_git_repo, sandbox_backend, repo_dir) + + if not is_git_repo: + # Directory exists but is not a valid git repo - remove and clone + logger.warning("Directory exists but is not a valid git repo at %s, removing", repo_dir) + try: + removed = await loop.run_in_executor(None, remove_directory, sandbox_backend, repo_dir) + if not removed: + msg = f"Failed to remove invalid directory at {repo_dir}" + logger.error(msg) + raise RuntimeError(msg) + logger.info("Removed invalid directory, will clone fresh repo") + except Exception: + logger.exception("Failed to remove invalid directory") + raise + # Fall through to clone below + else: + # Valid git repo exists, check for uncommitted changes + logger.info("Repo exists at %s, checking for uncommitted changes", repo_dir) + has_changes = await loop.run_in_executor( + None, git_has_uncommitted_changes, sandbox_backend, repo_dir ) - logger.debug("Git status result: exit_code=%s", status_result.exit_code) - except Exception: - logger.exception("Failed to get git status") - raise - # CRITICAL: Ensure remote URL doesn't contain token (clean up from previous runs) - clean_url = f"https://github.com/{owner}/{repo}.git" - try: - await loop.run_in_executor( - None, - sandbox_backend.execute, - f"cd {repo_dir} && git remote set-url origin {clean_url}", - ) - except Exception: - logger.exception("Failed to set remote URL") - raise + if has_changes: + logger.warning("Repo has uncommitted changes at %s, skipping pull", repo_dir) + return repo_dir - if status_result.exit_code == 0 and not status_result.output.strip(): + # No uncommitted changes, safe to pull + logger.info("Repo is clean, pulling latest changes from %s/%s", owner, repo) + + # CRITICAL: Ensure remote URL doesn't contain token (clean up from previous runs) + clean_url = f"https://github.com/{owner}/{repo}.git" + try: + await loop.run_in_executor( + None, + sandbox_backend.execute, + f"cd {repo_dir} && git remote set-url origin {clean_url}", + ) + except Exception: + logger.exception("Failed to set remote URL") + raise + + # Pull with authenticated URL auth_url = f"https://git:{token}@github.com/{owner}/{repo}.git" try: pull_result = await loop.run_in_executor( @@ -129,35 +145,40 @@ async def _clone_or_pull_repo_in_sandbox( # noqa: PLR0915 except Exception: logger.exception("Failed to execute git pull") raise - else: - logger.info("Cloning repo %s/%s to %s", owner, repo, repo_dir) - clone_url = f"https://git:{token}@github.com/{owner}/{repo}.git" - try: - result = await loop.run_in_executor( - None, sandbox_backend.execute, f"git clone {clone_url} {repo_dir}" - ) - logger.debug("Git clone result: exit_code=%s", result.exit_code) - except Exception: - logger.exception("Failed to execute git clone") - raise - if result.exit_code != 0: - msg = f"Failed to clone repo {owner}/{repo}: {result.output}" - logger.error(msg) - raise RuntimeError(msg) + logger.info("Repo updated at %s", repo_dir) + return repo_dir - clean_url = f"https://github.com/{owner}/{repo}.git" - try: - await loop.run_in_executor( - None, - sandbox_backend.execute, - f"cd {repo_dir} && git remote set-url origin {clean_url}", - ) - except Exception: - logger.exception("Failed to set remote URL after clone") - raise + # Directory doesn't exist or was removed - clone it + logger.info("Cloning repo %s/%s to %s", owner, repo, repo_dir) + clone_url = f"https://git:{token}@github.com/{owner}/{repo}.git" + try: + result = await loop.run_in_executor( + None, sandbox_backend.execute, f"git clone {clone_url} {repo_dir}" + ) + logger.debug("Git clone result: exit_code=%s", result.exit_code) + except Exception: + logger.exception("Failed to execute git clone") + raise - logger.info("Repo setup complete at %s", repo_dir) + if result.exit_code != 0: + msg = f"Failed to clone repo {owner}/{repo}: {result.output}" + logger.error(msg) + raise RuntimeError(msg) + + # Clean up the remote URL to remove token + clean_url = f"https://github.com/{owner}/{repo}.git" + try: + await loop.run_in_executor( + None, + sandbox_backend.execute, + f"cd {repo_dir} && git remote set-url origin {clean_url}", + ) + except Exception: + logger.exception("Failed to set remote URL after clone") + raise + + logger.info("Repo cloned successfully at %s", repo_dir) return repo_dir diff --git a/apps/agent/agent/utils/github.py b/apps/agent/agent/utils/github.py index c3e1e3cb..79269a23 100644 --- a/apps/agent/agent/utils/github.py +++ b/apps/agent/agent/utils/github.py @@ -20,6 +20,28 @@ def _run_git(sandbox_backend: Any, repo_dir: str, command: str) -> Any: return sandbox_backend.execute(f"cd {repo_dir} && {command}") +def repo_directory_exists(sandbox_backend: Any, repo_dir: str) -> bool: + """Check if repository directory exists.""" + safe_repo_dir = shlex.quote(repo_dir) + result = sandbox_backend.execute(f"test -d {safe_repo_dir} && echo exists") + return result.exit_code == 0 and "exists" in result.output + + +def is_valid_git_repo(sandbox_backend: Any, repo_dir: str) -> bool: + """Check if directory is a valid git repository.""" + git_dir = f"{repo_dir}/.git" + safe_git_dir = shlex.quote(git_dir) + result = sandbox_backend.execute(f"test -d {safe_git_dir} && echo exists") + return result.exit_code == 0 and "exists" in result.output + + +def remove_directory(sandbox_backend: Any, repo_dir: str) -> bool: + """Remove a directory and all its contents.""" + safe_repo_dir = shlex.quote(repo_dir) + result = sandbox_backend.execute(f"rm -rf {safe_repo_dir}") + return result.exit_code == 0 + + def git_has_uncommitted_changes(sandbox_backend: Any, repo_dir: str) -> bool: """Check whether the repo has uncommitted changes.""" result = _run_git(sandbox_backend, repo_dir, "git status --porcelain") From 1a796c1a148e91a8c9d982f998bc26753a61ae78 Mon Sep 17 00:00:00 2001 From: aran-yogesh Date: Tue, 17 Feb 2026 11:24:22 -0800 Subject: [PATCH 2/3] Fix:repo sync auth flow and tighten git utility typing --- apps/agent/agent/server.py | 111 ++++++++++++++++--------------- apps/agent/agent/utils/github.py | 53 +++++++++------ 2 files changed, 87 insertions(+), 77 deletions(-) diff --git a/apps/agent/agent/server.py b/apps/agent/agent/server.py index e9a0c4ee..c45e22d7 100644 --- a/apps/agent/agent/server.py +++ b/apps/agent/agent/server.py @@ -50,7 +50,6 @@ from .utils.github import ( git_has_uncommitted_changes, is_valid_git_repo, remove_directory, - repo_directory_exists, ) @@ -82,43 +81,62 @@ async def _clone_or_pull_repo_in_sandbox( # noqa: PLR0915 repo_dir = f"/workspace/{repo}" - # Check if directory exists - logger.debug("Checking if repo directory exists at %s", repo_dir) - dir_exists = await loop.run_in_executor(None, repo_directory_exists, sandbox_backend, repo_dir) + is_git_repo = await loop.run_in_executor(None, is_valid_git_repo, sandbox_backend, repo_dir) - if dir_exists: - is_git_repo = await loop.run_in_executor(None, is_valid_git_repo, sandbox_backend, repo_dir) + if not is_git_repo: + # Directory missing or not a valid git repo - remove and clone + logger.warning( + "Repo directory missing or not a valid git repo at %s, removing", repo_dir + ) + try: + removed = await loop.run_in_executor(None, remove_directory, sandbox_backend, repo_dir) + if not removed: + msg = f"Failed to remove invalid directory at {repo_dir}" + logger.error(msg) + raise RuntimeError(msg) + logger.info("Removed invalid directory, will clone fresh repo") + except Exception: + logger.exception("Failed to remove invalid directory") + raise + # Fall through to clone below + else: + # Valid git repo exists, check for uncommitted changes + logger.info("Repo exists at %s, checking for uncommitted changes", repo_dir) + has_changes = await loop.run_in_executor( + None, git_has_uncommitted_changes, sandbox_backend, repo_dir + ) - if not is_git_repo: - # Directory exists but is not a valid git repo - remove and clone - logger.warning("Directory exists but is not a valid git repo at %s, removing", repo_dir) - try: - removed = await loop.run_in_executor(None, remove_directory, sandbox_backend, repo_dir) - if not removed: - msg = f"Failed to remove invalid directory at {repo_dir}" - logger.error(msg) - raise RuntimeError(msg) - logger.info("Removed invalid directory, will clone fresh repo") - except Exception: - logger.exception("Failed to remove invalid directory") - raise - # Fall through to clone below - else: - # Valid git repo exists, check for uncommitted changes - logger.info("Repo exists at %s, checking for uncommitted changes", repo_dir) - has_changes = await loop.run_in_executor( - None, git_has_uncommitted_changes, sandbox_backend, repo_dir + if has_changes: + logger.warning("Repo has uncommitted changes at %s, skipping pull", repo_dir) + return repo_dir + + # No uncommitted changes, safe to pull + logger.info("Repo is clean, pulling latest changes from %s/%s", owner, repo) + + auth_url = f"https://git:{token}@github.com/{owner}/{repo}.git" + clean_url = f"https://github.com/{owner}/{repo}.git" + + # Set authenticated URL for private repos, then restore clean URL. + try: + await loop.run_in_executor( + None, + sandbox_backend.execute, + f"cd {repo_dir} && git remote set-url origin {auth_url}", ) - - if has_changes: - logger.warning("Repo has uncommitted changes at %s, skipping pull", repo_dir) - return repo_dir - - # No uncommitted changes, safe to pull - logger.info("Repo is clean, pulling latest changes from %s/%s", owner, repo) - - # CRITICAL: Ensure remote URL doesn't contain token (clean up from previous runs) - clean_url = f"https://github.com/{owner}/{repo}.git" + pull_result = await loop.run_in_executor( + None, sandbox_backend.execute, f"cd {repo_dir} && git pull origin" + ) + logger.debug("Git pull result: exit_code=%s", pull_result.exit_code) + if pull_result.exit_code != 0: + logger.warning( + "Git pull failed with exit code %s: %s", + pull_result.exit_code, + pull_result.output[:200] if pull_result.output else "", + ) + except Exception: + logger.exception("Failed to execute git pull") + raise + finally: try: await loop.run_in_executor( None, @@ -126,28 +144,11 @@ async def _clone_or_pull_repo_in_sandbox( # noqa: PLR0915 f"cd {repo_dir} && git remote set-url origin {clean_url}", ) except Exception: - logger.exception("Failed to set remote URL") + logger.exception("Failed to restore clean remote URL") raise - # Pull with authenticated URL - auth_url = f"https://git:{token}@github.com/{owner}/{repo}.git" - try: - pull_result = await loop.run_in_executor( - None, sandbox_backend.execute, f"cd {repo_dir} && git pull {auth_url}" - ) - logger.debug("Git pull result: exit_code=%s", pull_result.exit_code) - if pull_result.exit_code != 0: - logger.warning( - "Git pull failed with exit code %s: %s", - pull_result.exit_code, - pull_result.output[:200] if pull_result.output else "", - ) - except Exception: - logger.exception("Failed to execute git pull") - raise - - logger.info("Repo updated at %s", repo_dir) - return repo_dir + logger.info("Repo updated at %s", repo_dir) + return repo_dir # Directory doesn't exist or was removed - clone it logger.info("Cloning repo %s/%s to %s", owner, repo, repo_dir) diff --git a/apps/agent/agent/utils/github.py b/apps/agent/agent/utils/github.py index 79269a23..cee7eda5 100644 --- a/apps/agent/agent/utils/github.py +++ b/apps/agent/agent/utils/github.py @@ -4,9 +4,9 @@ from __future__ import annotations import logging import shlex -from typing import Any import httpx +from deepagents.backends.protocol import ExecuteResponse, SandboxBackendProtocol logger = logging.getLogger(__name__) @@ -15,19 +15,14 @@ HTTP_CREATED = 201 HTTP_UNPROCESSABLE_ENTITY = 422 -def _run_git(sandbox_backend: Any, repo_dir: str, command: str) -> Any: +def _run_git( + sandbox_backend: SandboxBackendProtocol, repo_dir: str, command: str +) -> ExecuteResponse: """Run a git command in the sandbox repo directory.""" return sandbox_backend.execute(f"cd {repo_dir} && {command}") -def repo_directory_exists(sandbox_backend: Any, repo_dir: str) -> bool: - """Check if repository directory exists.""" - safe_repo_dir = shlex.quote(repo_dir) - result = sandbox_backend.execute(f"test -d {safe_repo_dir} && echo exists") - return result.exit_code == 0 and "exists" in result.output - - -def is_valid_git_repo(sandbox_backend: Any, repo_dir: str) -> bool: +def is_valid_git_repo(sandbox_backend: SandboxBackendProtocol, repo_dir: str) -> bool: """Check if directory is a valid git repository.""" git_dir = f"{repo_dir}/.git" safe_git_dir = shlex.quote(git_dir) @@ -35,25 +30,31 @@ def is_valid_git_repo(sandbox_backend: Any, repo_dir: str) -> bool: return result.exit_code == 0 and "exists" in result.output -def remove_directory(sandbox_backend: Any, repo_dir: str) -> bool: +def remove_directory(sandbox_backend: SandboxBackendProtocol, repo_dir: str) -> bool: """Remove a directory and all its contents.""" safe_repo_dir = shlex.quote(repo_dir) result = sandbox_backend.execute(f"rm -rf {safe_repo_dir}") return result.exit_code == 0 -def git_has_uncommitted_changes(sandbox_backend: Any, repo_dir: str) -> bool: +def git_has_uncommitted_changes( + sandbox_backend: SandboxBackendProtocol, repo_dir: str +) -> bool: """Check whether the repo has uncommitted changes.""" result = _run_git(sandbox_backend, repo_dir, "git status --porcelain") return result.exit_code == 0 and bool(result.output.strip()) -def git_fetch_origin(sandbox_backend: Any, repo_dir: str) -> Any: +def git_fetch_origin( + sandbox_backend: SandboxBackendProtocol, repo_dir: str +) -> ExecuteResponse: """Fetch latest from origin (best-effort).""" return _run_git(sandbox_backend, repo_dir, "git fetch origin 2>/dev/null || true") -def git_has_unpushed_commits(sandbox_backend: Any, repo_dir: str) -> bool: +def git_has_unpushed_commits( + sandbox_backend: SandboxBackendProtocol, repo_dir: str +) -> bool: """Check whether there are commits not pushed to upstream.""" git_log_cmd = ( "git log --oneline @{upstream}..HEAD 2>/dev/null " @@ -63,13 +64,15 @@ def git_has_unpushed_commits(sandbox_backend: Any, repo_dir: str) -> bool: return result.exit_code == 0 and bool(result.output.strip()) -def git_current_branch(sandbox_backend: Any, repo_dir: str) -> str: +def git_current_branch(sandbox_backend: SandboxBackendProtocol, repo_dir: str) -> str: """Get the current git branch name.""" result = _run_git(sandbox_backend, repo_dir, "git rev-parse --abbrev-ref HEAD") return result.output.strip() if result.exit_code == 0 else "" -def git_checkout_branch(sandbox_backend: Any, repo_dir: str, branch: str) -> bool: +def git_checkout_branch( + sandbox_backend: SandboxBackendProtocol, repo_dir: str, branch: str +) -> bool: """Checkout branch, creating it if needed.""" safe_branch = shlex.quote(branch) checkout_result = _run_git( @@ -82,7 +85,7 @@ def git_checkout_branch(sandbox_backend: Any, repo_dir: str, branch: str) -> boo def git_config_user( - sandbox_backend: Any, + sandbox_backend: SandboxBackendProtocol, repo_dir: str, name: str, email: str, @@ -94,18 +97,24 @@ def git_config_user( _run_git(sandbox_backend, repo_dir, f"git config user.email {safe_email}") -def git_add_all(sandbox_backend: Any, repo_dir: str) -> Any: +def git_add_all( + sandbox_backend: SandboxBackendProtocol, repo_dir: str +) -> ExecuteResponse: """Stage all changes.""" return _run_git(sandbox_backend, repo_dir, "git add -A") -def git_commit(sandbox_backend: Any, repo_dir: str, message: str) -> Any: +def git_commit( + sandbox_backend: SandboxBackendProtocol, repo_dir: str, message: str +) -> ExecuteResponse: """Commit staged changes with the given message.""" safe_message = shlex.quote(message) return _run_git(sandbox_backend, repo_dir, f"git commit -m {safe_message}") -def git_get_remote_url(sandbox_backend: Any, repo_dir: str) -> str | None: +def git_get_remote_url( + sandbox_backend: SandboxBackendProtocol, repo_dir: str +) -> str | None: """Get the origin remote URL.""" result = _run_git(sandbox_backend, repo_dir, "git remote get-url origin") if result.exit_code != 0: @@ -114,11 +123,11 @@ def git_get_remote_url(sandbox_backend: Any, repo_dir: str) -> str | None: def git_push( - sandbox_backend: Any, + sandbox_backend: SandboxBackendProtocol, repo_dir: str, branch: str, github_token: str | None = None, -) -> Any: +) -> ExecuteResponse: """Push the branch to origin, using a token if needed.""" safe_branch = shlex.quote(branch) remote_url = git_get_remote_url(sandbox_backend, repo_dir) From d319553198eeab26cd53377c0513621899915535 Mon Sep 17 00:00:00 2001 From: aran-yogesh Date: Tue, 17 Feb 2026 14:42:31 -0800 Subject: [PATCH 3/3] comment changes and var reuse --- apps/agent/agent/server.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/apps/agent/agent/server.py b/apps/agent/agent/server.py index c45e22d7..d8c319ee 100644 --- a/apps/agent/agent/server.py +++ b/apps/agent/agent/server.py @@ -80,11 +80,12 @@ async def _clone_or_pull_repo_in_sandbox( # noqa: PLR0915 raise ValueError(msg) repo_dir = f"/workspace/{repo}" + auth_url = f"https://git:{token}@github.com/{owner}/{repo}.git" + clean_url = f"https://github.com/{owner}/{repo}.git" is_git_repo = await loop.run_in_executor(None, is_valid_git_repo, sandbox_backend, repo_dir) if not is_git_repo: - # Directory missing or not a valid git repo - remove and clone logger.warning( "Repo directory missing or not a valid git repo at %s, removing", repo_dir ) @@ -98,9 +99,7 @@ async def _clone_or_pull_repo_in_sandbox( # noqa: PLR0915 except Exception: logger.exception("Failed to remove invalid directory") raise - # Fall through to clone below else: - # Valid git repo exists, check for uncommitted changes logger.info("Repo exists at %s, checking for uncommitted changes", repo_dir) has_changes = await loop.run_in_executor( None, git_has_uncommitted_changes, sandbox_backend, repo_dir @@ -110,13 +109,8 @@ async def _clone_or_pull_repo_in_sandbox( # noqa: PLR0915 logger.warning("Repo has uncommitted changes at %s, skipping pull", repo_dir) return repo_dir - # No uncommitted changes, safe to pull logger.info("Repo is clean, pulling latest changes from %s/%s", owner, repo) - auth_url = f"https://git:{token}@github.com/{owner}/{repo}.git" - clean_url = f"https://github.com/{owner}/{repo}.git" - - # Set authenticated URL for private repos, then restore clean URL. try: await loop.run_in_executor( None, @@ -150,12 +144,10 @@ async def _clone_or_pull_repo_in_sandbox( # noqa: PLR0915 logger.info("Repo updated at %s", repo_dir) return repo_dir - # Directory doesn't exist or was removed - clone it logger.info("Cloning repo %s/%s to %s", owner, repo, repo_dir) - clone_url = f"https://git:{token}@github.com/{owner}/{repo}.git" try: result = await loop.run_in_executor( - None, sandbox_backend.execute, f"git clone {clone_url} {repo_dir}" + None, sandbox_backend.execute, f"git clone {auth_url} {repo_dir}" ) logger.debug("Git clone result: exit_code=%s", result.exit_code) except Exception: @@ -167,8 +159,6 @@ async def _clone_or_pull_repo_in_sandbox( # noqa: PLR0915 logger.error(msg) raise RuntimeError(msg) - # Clean up the remote URL to remove token - clean_url = f"https://github.com/{owner}/{repo}.git" try: await loop.run_in_executor( None,