fix: refresh provider tokens proactively and update git URLs on resume (#11296)

This commit is contained in:
Alona
2025-10-21 14:19:08 -04:00
committed by GitHub
parent 49f360d021
commit 267528fa82
5 changed files with 123 additions and 27 deletions

View File

@@ -8,7 +8,11 @@ from openhands.core.config import OpenHandsConfig
from openhands.core.config.mcp_config import MCPConfig, MCPStdioServerConfig
from openhands.events.action import Action
from openhands.events.action.commands import CmdRunAction
from openhands.events.observation import NullObservation, Observation
from openhands.events.observation import (
CmdOutputObservation,
NullObservation,
Observation,
)
from openhands.events.stream import EventStream
from openhands.integrations.provider import ProviderHandler, ProviderToken, ProviderType
from openhands.integrations.service_types import AuthenticationError, Repository
@@ -73,6 +77,36 @@ class MockRuntime(Runtime):
def run_action(self, action: Action) -> Observation:
self.run_action_calls.append(action)
# Return a mock git remote URL for git remote get-url commands
# Use an OLD token to simulate token refresh scenario
if (
isinstance(action, CmdRunAction)
and 'git remote get-url origin' in action.command
):
# Extract provider from previous clone command
if len(self.run_action_calls) > 0:
clone_cmd = (
self.run_action_calls[0].command if self.run_action_calls else ''
)
if 'github.com' in clone_cmd:
mock_url = 'https://old_github_token@github.com/owner/repo.git'
elif 'gitlab.com' in clone_cmd:
mock_url = (
'https://oauth2:old_gitlab_token@gitlab.com/owner/repo.git'
)
else:
mock_url = 'https://github.com/owner/repo.git'
return CmdOutputObservation(
content=mock_url, command_id=-1, command='', exit_code=0
)
# Return success for git remote set-url commands
if (
isinstance(action, CmdRunAction)
and 'git remote set-url origin' in action.command
):
return CmdOutputObservation(
content='', command_id=-1, command='', exit_code=0
)
return NullObservation(content='')
def call_tool_mcp(self, action):
@@ -330,22 +364,29 @@ async def test_clone_or_init_repo_github_with_token(temp_dir, monkeypatch):
result = await runtime.clone_or_init_repo(git_provider_tokens, 'owner/repo', None)
# Verify that git clone and checkout were called as separate commands
assert len(runtime.run_action_calls) == 2
# Verify that git clone, checkout, and git remote URL update were called
assert len(runtime.run_action_calls) == 3 # clone, checkout, set-url
assert isinstance(runtime.run_action_calls[0], CmdRunAction)
assert isinstance(runtime.run_action_calls[1], CmdRunAction)
assert isinstance(runtime.run_action_calls[2], CmdRunAction)
# Check that the first command is the git clone with the correct URL format with token
clone_cmd = runtime.run_action_calls[0].command
assert (
f'git clone https://{github_token}@github.com/owner/repo.git repo' in clone_cmd
)
assert f'https://{github_token}@github.com/owner/repo.git' in clone_cmd
expected_repo_path = str(runtime.workspace_root / 'repo')
assert expected_repo_path in clone_cmd
# Check that the second command is the checkout
checkout_cmd = runtime.run_action_calls[1].command
assert 'cd repo' in checkout_cmd
assert f'cd {expected_repo_path}' in checkout_cmd
assert 'git checkout -b openhands-workspace-' in checkout_cmd
# Check that the third command sets the remote URL immediately after clone
set_url_cmd = runtime.run_action_calls[2].command
assert f'cd {expected_repo_path}' in set_url_cmd
assert 'git remote set-url origin' in set_url_cmd
assert github_token in set_url_cmd
assert result == 'repo'
@@ -363,20 +404,28 @@ async def test_clone_or_init_repo_github_no_token(temp_dir, monkeypatch):
mock_repo_and_patch(monkeypatch, provider=ProviderType.GITHUB)
result = await runtime.clone_or_init_repo(None, 'owner/repo', None)
# Verify that git clone and checkout were called as separate commands
assert len(runtime.run_action_calls) == 2
# Verify that git clone, checkout, and remote update were called
assert len(runtime.run_action_calls) == 3 # clone, checkout, set-url
assert isinstance(runtime.run_action_calls[0], CmdRunAction)
assert isinstance(runtime.run_action_calls[1], CmdRunAction)
assert isinstance(runtime.run_action_calls[2], CmdRunAction)
# Check that the first command is the git clone with the correct URL format without token
clone_cmd = runtime.run_action_calls[0].command
assert 'git clone https://github.com/owner/repo.git repo' in clone_cmd
expected_repo_path = str(runtime.workspace_root / 'repo')
assert 'git clone https://github.com/owner/repo.git' in clone_cmd
assert expected_repo_path in clone_cmd
# Check that the second command is the checkout
checkout_cmd = runtime.run_action_calls[1].command
assert 'cd repo' in checkout_cmd
assert f'cd {expected_repo_path}' in checkout_cmd
assert 'git checkout -b openhands-workspace-' in checkout_cmd
# Check that the third command sets the remote URL after clone
set_url_cmd = runtime.run_action_calls[2].command
assert f'cd {expected_repo_path}' in set_url_cmd
assert 'git remote set-url origin' in set_url_cmd
assert result == 'repo'
@@ -403,23 +452,29 @@ async def test_clone_or_init_repo_gitlab_with_token(temp_dir, monkeypatch):
result = await runtime.clone_or_init_repo(git_provider_tokens, 'owner/repo', None)
# Verify that git clone and checkout were called as separate commands
assert len(runtime.run_action_calls) == 2
# Verify that git clone, checkout, and git remote URL update were called
assert len(runtime.run_action_calls) == 3 # clone, checkout, set-url
assert isinstance(runtime.run_action_calls[0], CmdRunAction)
assert isinstance(runtime.run_action_calls[1], CmdRunAction)
assert isinstance(runtime.run_action_calls[2], CmdRunAction)
# Check that the first command is the git clone with the correct URL format with token
clone_cmd = runtime.run_action_calls[0].command
assert (
f'git clone https://oauth2:{gitlab_token}@gitlab.com/owner/repo.git repo'
in clone_cmd
)
expected_repo_path = str(runtime.workspace_root / 'repo')
assert f'https://oauth2:{gitlab_token}@gitlab.com/owner/repo.git' in clone_cmd
assert expected_repo_path in clone_cmd
# Check that the second command is the checkout
checkout_cmd = runtime.run_action_calls[1].command
assert 'cd repo' in checkout_cmd
assert f'cd {expected_repo_path}' in checkout_cmd
assert 'git checkout -b openhands-workspace-' in checkout_cmd
# Check that the third command sets the remote URL immediately after clone
set_url_cmd = runtime.run_action_calls[2].command
assert f'cd {expected_repo_path}' in set_url_cmd
assert 'git remote set-url origin' in set_url_cmd
assert gitlab_token in set_url_cmd
assert result == 'repo'
@@ -437,18 +492,24 @@ async def test_clone_or_init_repo_with_branch(temp_dir, monkeypatch):
mock_repo_and_patch(monkeypatch, provider=ProviderType.GITHUB)
result = await runtime.clone_or_init_repo(None, 'owner/repo', 'feature-branch')
# Verify that git clone and checkout were called as separate commands
assert len(runtime.run_action_calls) == 2
# Verify that git clone, checkout, and remote update were called
assert len(runtime.run_action_calls) == 3 # clone, checkout, set-url
assert isinstance(runtime.run_action_calls[0], CmdRunAction)
assert isinstance(runtime.run_action_calls[1], CmdRunAction)
assert isinstance(runtime.run_action_calls[2], CmdRunAction)
# Check that the first command is the git clone
clone_cmd = runtime.run_action_calls[0].command
expected_repo_path = str(runtime.workspace_root / 'repo')
assert 'git clone https://github.com/owner/repo.git' in clone_cmd
assert expected_repo_path in clone_cmd
# Check that the second command contains the correct branch checkout
checkout_cmd = runtime.run_action_calls[1].command
assert 'git clone https://github.com/owner/repo.git repo' in clone_cmd
assert 'cd repo' in checkout_cmd
assert f'cd {expected_repo_path}' in checkout_cmd
assert 'git checkout feature-branch' in checkout_cmd
set_url_cmd = runtime.run_action_calls[2].command
assert f'cd {expected_repo_path}' in set_url_cmd
assert 'git remote set-url origin' in set_url_cmd
assert 'git checkout -b' not in checkout_cmd # Should not create a new branch
assert result == 'repo'