Skip to content

Convert GitHub client to async using reqwest and tokio#238

Open
kdeal wants to merge 4 commits intomainfrom
codex/convert-github-client-to-async
Open

Convert GitHub client to async using reqwest and tokio#238
kdeal wants to merge 4 commits intomainfrom
codex/convert-github-client-to-async

Conversation

@kdeal
Copy link
Owner

@kdeal kdeal commented Feb 7, 2026

Motivation

  • Replace blocking HTTP calls to GitHub with async requests to avoid blocking the CLI and enable concurrent operations.
  • Use reqwest for async HTTP and GraphQL requests and run them on a tokio runtime for compatibility with other async code.

Description

  • Replaced synchronous ureq-based GitHub API calls with an async reqwest::Client stored on GitHubClient and converted api_get and graphql_query to async methods in src/github.rs.
  • Made GitHub-facing methods async (get_pull_requests_for_commit, get_pr_comments, get_issue_comments, get_review_comments) and updated internal GraphQL pagination to await requests.
  • Updated callers to the GitHub client by making prune_merged_branches, get_pull_request_for_commit, and get_pr_comments async and awaiting the client calls in src/actions.rs.
  • Turned the CLI entrypoint into an async main with #[tokio::main] and awaited the modified action functions in src/main.rs.
  • Added reqwest and tokio dependencies to Cargo.toml to support async HTTP and runtime usage.

Testing

  • No automated tests were run on the modified code as part of this change.

Codex Task

Summary by CodeRabbit

  • New Features

    • Default branch is now protected when pruning merged branches.
  • Improvements

    • Branch pruning and pull-request workflows run asynchronously for better responsiveness.
    • API interactions use a persistent HTTP client with configured headers and timeouts for more efficient requests.
    • Pull-request comment and review fetching is concurrent and more resilient; API errors now fail gracefully to avoid disrupting operations.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 7, 2026

Walkthrough

This PR migrates GitHub HTTP interactions to async/await and introduces reqwest and tokio as dependencies. It replaces the previous token-based client with a configured reqwest::Client stored on GitHubClient, converts API methods (api_get, get_pull_requests_for_commit, get_pr_comments, get_issue_comments, get_review_comments, graphql_query) to async and to use .json().await parsing, and updates action functions (prune_merged_branches, get_pull_request_for_commit, get_pr_comments) and main to async with .await call sites. prune_merged_branches now skips the default branch when pruning and continues on GitHub API failures.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (5 files):

⚔️ wkfl/Cargo.lock (content)
⚔️ wkfl/Cargo.toml (content)
⚔️ wkfl/src/actions.rs (content)
⚔️ wkfl/src/github.rs (content)
⚔️ wkfl/src/main.rs (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: converting GitHub client operations from synchronous to asynchronous using reqwest and tokio libraries.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/convert-github-client-to-async
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch codex/convert-github-client-to-async
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wkfl/src/main.rs (1)

428-435: ⚠️ Potential issue | 🟡 Minor

Fix formatting to pass CI — cargo fmt check is failing on these lines.

The pipeline reports that cargo fmt --check failed on lines 425–441. Run cargo fmt to auto-fix the formatting of this .await? chain.

🧹 Nitpick comments (2)
wkfl/src/github.rs (2)

88-93: Consider configuring default headers and a timeout on the reqwest::Client.

The Authorization, User-Agent, and Accept headers are duplicated in both api_get (lines 108–110) and graphql_query (lines 250–252). Setting them as default headers on the client eliminates this repetition. Additionally, Client::new() has no timeout, so a hung GitHub API connection will block the tokio runtime indefinitely.

♻️ Proposed refactor
+use reqwest::header::{self, HeaderMap, HeaderValue};
+
 pub fn new(host: String, token: String) -> Self {
     // ...base URL logic unchanged...
+    let mut headers = HeaderMap::new();
+    headers.insert(
+        header::AUTHORIZATION,
+        HeaderValue::from_str(&format!("Bearer {}", token)).expect("valid header"),
+    );
+    headers.insert(header::USER_AGENT, HeaderValue::from_static("wkfl"));
+    headers.insert(
+        header::ACCEPT,
+        HeaderValue::from_static("application/vnd.github+json"),
+    );
     GitHubClient {
         api_base,
         graphql_base,
         token,
-        client: Client::new(),
+        client: Client::builder()
+            .default_headers(headers)
+            .timeout(std::time::Duration::from_secs(30))
+            .build()
+            .expect("Failed to build HTTP client"),
     }
 }

Then remove the per-request .header(...) calls from api_get and graphql_query.


142-155: Consider fetching issue comments and review comments concurrently.

get_issue_comments and get_review_comments are independent calls that are currently awaited sequentially. Using tokio::try_join! would execute them in parallel, roughly halving the latency.

♻️ Proposed refactor
     pub async fn get_pr_comments(
         &self,
         owner: &str,
         repo: &str,
         pr_number: u64,
     ) -> Result<PrComments> {
-        let issue_comments = self.get_issue_comments(owner, repo, pr_number).await?;
-        let review_comments = self.get_review_comments(owner, repo, pr_number).await?;
+        let (issue_comments, review_comments) = tokio::try_join!(
+            self.get_issue_comments(owner, repo, pr_number),
+            self.get_review_comments(owner, repo, pr_number),
+        )?;
 
         Ok(PrComments {
             issue_comments,
             review_comments,
         })
     }

@socket-security
Copy link

socket-security bot commented Feb 7, 2026

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: cargo hyper-util is 98.0% likely obfuscated

Confidence: 0.98

Location: Package overview

From: ?cargo/hyper-util@0.1.20

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore cargo/hyper-util@0.1.20. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wkfl/src/github.rs (1)

174-197: ⚠️ Potential issue | 🟡 Minor

get_issue_comments does not handle pagination — may silently truncate results.

The GitHub REST API returns a maximum of 30 items per page by default. For PRs with more than 30 timeline comments, this method will only return the first page. This is inconsistent with get_review_comments (line 200) which properly paginates via GraphQL.

Consider either adding per_page=100 as a query parameter (to raise the limit) and Link header–based pagination, or migrating this endpoint to GraphQL with cursor pagination like the review comments.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant