Skip to content

[rust-guard] Rust Guard: Two dead-code / duplication fixes in backend.rs #4329

@github-actions

Description

@github-actions

🦀 Rust Guard Improvement Report

Improvement 1: Remove Write-Only repo_owner_type_cache

Category: Dead Code
File(s): guards/github-guard/rust-guard/src/labels/backend.rs
Effort: Small (< 15 min)
Risk: Low

Problem

backend.rs maintains three process-wide static caches, but only two are ever read. The owner-type cache (repo_owner_type_cache, line 22) is written to on every is_repo_private_with_callback call via set_cached_owner_is_org (line 63), yet there is no corresponding get_cached_owner_is_org reader anywhere in the codebase. Every is_repo_private_with_callback invocation:

  1. Locks the Mutex to write an entry (wasted Mutex acquisition)
  2. Grows the HashMap indefinitely over the process lifetime (wasted memory)
  3. Keeps dead functions (repo_owner_type_cache, set_cached_owner_is_org) around for no benefit

This is a classic incomplete-feature footgun: the infrastructure was added in anticipation of a reader that was never wired up.

Suggested Change

Remove repo_owner_type_cache, set_cached_owner_is_org, and the call site inside is_repo_private_with_callback. Preserve the debug log by retaining extract_owner_is_org inline (no allocation needed), or drop the whole block if the log is not valuable.

Before

// backend.rs lines 22-26
fn repo_owner_type_cache() -> &'static Mutex<HashMap<String, bool>> {
    static CACHE: OnceLock<Mutex<HashMap<String, bool>>> = OnceLock::new();
    CACHE.get_or_init(|| Mutex::new(HashMap::new()))
}

// lines 63-67
fn set_cached_owner_is_org(repo_id: &str, is_org: bool) {
    if let Ok(mut cache) = repo_owner_type_cache().lock() {
        cache.insert(repo_id.to_string(), is_org);
    }
}

// lines 222-229 (inside is_repo_private_with_callback)
if let Some(is_org) = extract_owner_is_org(&response, &repo_id) {
    set_cached_owner_is_org(&repo_id, is_org);
    crate::log_debug(&format!(
        "Repo owner type for {}: {}",
        repo_id,
        if is_org { "Organization" } else { "User" }
    ));
}

After

// Remove repo_owner_type_cache and set_cached_owner_is_org entirely.

// lines 222-229 (inside is_repo_private_with_callback) — keep log, drop cache write
if let Some(is_org) = extract_owner_is_org(&response, &repo_id) {
    crate::log_debug(&format!(
        "Repo owner type for {}: {}",
        repo_id,
        if is_org { "Organization" } else { "User" }
    ));
}

Why This Matters

In WASM, heap is a finite shared resource. A HashMap that grows without bound and is never consulted wastes both memory and Mutex lock cycles on every repo-visibility check. Removing it eliminates dead code and tightens the module's invariant: every static cache has both a writer and a reader.


Improvement 2: Delegate get_issue_author_association_with_callback to get_issue_author_info_with_callback

Category: Duplication
File(s): guards/github-guard/rust-guard/src/labels/backend.rs
Effort: Small (< 15 min)
Risk: Low

Problem

get_issue_author_association_with_callback (lines 344–391) is a strict subset of get_issue_author_info_with_callback (lines 393–450). Both functions:

  • Validate the same three inputs (owner, repo, issue_number)
  • Construct the same JSON args (owner, repo, issue_number, method: "get")
  • Allocate the same SMALL_BUFFER_SIZE buffer
  • Call the same backend tool ("issue_read")
  • Parse the response via identical UTF-8 → JSON → extract_mcp_response chain
  • Extract author_association via the same two-field fallback (author_association / authorAssociation)

The only difference is that get_issue_author_info_with_callback also extracts author_login. This 35-line duplication means any future change to the issue_read call shape (error handling, buffer size, field names) must be applied in two places.

Suggested Change

Replace the full implementation of get_issue_author_association_with_callback with a one-line delegate to get_issue_author_info_with_callback.

Before

// backend.rs lines 344–391
pub fn get_issue_author_association_with_callback(
    callback: GithubMcpCallback,
    owner: &str,
    repo: &str,
    issue_number: &str,
) -> Option<String> {
    if owner.is_empty() || repo.is_empty() || issue_number.is_empty() {
        return None;
    }

    let args = serde_json::json!({
        "owner": owner,
        "repo": repo,
        "issue_number": issue_number,
        "method": "get",
    });

    let args_str = args.to_string();
    let mut result_buffer = vec![0u8; SMALL_BUFFER_SIZE];

    let len = match callback("issue_read", &args_str, &mut result_buffer) {
        Ok(len) if len > 0 => len,
        _ => return None,
    };

    let response_str = std::str::from_utf8(&result_buffer[..len]).ok()?;
    let response = serde_json::from_str::<Value>(response_str).ok()?;
    let issue = super::extract_mcp_response(&response);

    issue
        .get("author_association")
        .or_else(|| issue.get("authorAssociation"))
        .and_then(|v| v.as_str())
        .map(String::from)
}

After

pub fn get_issue_author_association_with_callback(
    callback: GithubMcpCallback,
    owner: &str,
    repo: &str,
    issue_number: &str,
) -> Option<String> {
    get_issue_author_info_with_callback(callback, owner, repo, issue_number)
        .and_then(|info| info.author_association)
}

Why This Matters

This turns 35 lines into 3 and makes the single source of truth explicit. The call site in helpers.rs (line 1591) continues to call get_issue_author_association unchanged — only the internal implementation is simplified. Future changes to the issue_read fetch path need to be made in exactly one place.


Codebase Health Summary

  • Total Rust files: 9
  • Total lines: 12,832
  • Areas analyzed: backend.rs, constants.rs, tools.rs, helpers.rs, lib.rs, mod.rs, response_paths.rs, response_items.rs, tool_rules.rs
  • Areas with no further improvements: none identified yet

Generated by Rust Guard Improver • Run: §24771359121

Generated by Rust Guard Improver · ● 1.3M ·

  • expires on Apr 29, 2026, 9:44 AM UTC

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions