Skip to content

Conversation

@zeevmoney
Copy link

Summary

  • Add asyncio.wait_for timeout to pygit2 clone_repository and fetch operations in GitPolicyFetcher, preventing indefinite hangs when a git repo is unreachable
  • Wire existing POLICY_REPO_CLONE_TIMEOUT config (previously unused for scoped repos) into GitPolicyFetcher via ScopesService
  • Re-raise pygit2.GitError and asyncio.TimeoutError from _clone() instead of silently swallowing errors, so sync_scope can handle failures properly

Linear Issue

https://linear.app/permit/issue/PER-13817/fix-git-clonefetch-operations-hanging-indefinitely-on

Tests

  • Unit tests added for run_sync_with_timeout (5 tests)
  • Unit tests added for GitPolicyFetcher clone/fetch timeout and error handling (5 tests)
  • Mocks used for all external dependencies (pygit2, git operations)
  • Integration tests: not needed — changes are internal timeout/error handling
  • Test infrastructure: existing pytest + pytest-asyncio

Test plan

  • Verify POLICY_REPO_CLONE_TIMEOUT=0 (default) preserves existing behavior — no timeout applied
  • Verify POLICY_REPO_CLONE_TIMEOUT=30 causes clone/fetch to abort after 30s on unreachable repos
  • Verify error logs include clear timeout/failure messages
  • Verify scopes sharing the same repo lock are unblocked when a clone times out

🤖 Generated with Claude Code

Add timeout support to server-side pygit2 clone and fetch operations
via asyncio.wait_for. Wire existing POLICY_REPO_CLONE_TIMEOUT config
into GitPolicyFetcher. Re-raise errors from _clone() instead of
silently swallowing them so callers can handle failures properly.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@linear
Copy link

linear bot commented Feb 5, 2026

@netlify
Copy link

netlify bot commented Feb 5, 2026

Deploy Preview for opal-docs canceled.

Name Link
🔨 Latest commit 1cc1c18
🔍 Latest deploy log https://app.netlify.com/projects/opal-docs/deploys/6984079dc2bfb70008479941

)
try:
repo: Repository = await run_sync(
repo: Repository = await run_sync_with_timeout(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to make sure that in case of a timeout, we actually terminate the Git subprocess

datetime.datetime.now()
)
try:
await run_sync_with_timeout(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timeout should be for the entire sync procedure, not individually fetch and clone. That would naturally double the actual timeout

return False
return last_fetched > t

async def fetch_and_notify_on_changes(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should instrument this function

async def run_sync_with_timeout(
func: Callable[P_args, T_result],
*args: P_args.args,
timeout: Optional[float] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the upstream function have "timeout" kwarg then you can't pass it to it, make it more unique

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants