Skip to content

[duplicate-code] Duplicate Code Pattern: get_collaborator_permission GitHub REST Call Logic #4459

@github-actions

Description

@github-actions

Part of duplicate code analysis: #4458

Summary

The logic for making a GitHub REST API call to fetch collaborator permission (/repos/{owner}/{repo}/collaborators/{username}/permission) is nearly identical in two files, with ~35 lines duplicated.

Duplication Details

Pattern: get_collaborator_permission HTTP call logic

  • Severity: High
  • Occurrences: 2
  • Locations:
    • internal/server/unified.gocallCollaboratorPermission method (lines ~282–337)
    • internal/proxy/proxy.gocase "get_collaborator_permission" inside restBackendCaller.CallTool (lines ~278–328)

Both locations perform:

  1. Parse owner, repo, username from argsMap
  2. Validate all three fields are non-empty
  3. Build the path /repos/{owner}/{repo}/collaborators/{username}/permission
  4. Make a GET request to the GitHub API
  5. Read the response body
  6. Return error for HTTP 4xx/5xx
  7. Call mcp.LogAndWrapCollaboratorPermission

internal/server/unified.go (callCollaboratorPermission):

argsMap, ok := args.(map[string]interface{})
owner, _ := argsMap["owner"].(string)
repo, _ := argsMap["repo"].(string)
username, _ := argsMap["username"].(string)
if owner == "" || repo == "" || username == "" {
    return nil, fmt.Errorf("get_collaborator_permission: missing owner/repo/username")
}
token := envutil.LookupGitHubToken()
apiURL := envutil.DeriveGitHubAPIURL(envutil.DefaultGitHubAPIBaseURL)
path := fmt.Sprintf("/repos/%s/%s/collaborators/%s/permission", owner, repo, username)
req, _ := http.NewRequestWithContext(ctx, "GET", apiURL+path, nil)
req.Header.Set("Authorization", "token "+token)
req.Header.Set("Accept", "application/vnd.github+json")
resp, _ := http.DefaultClient.Do(req)
// ... read body, check status, call LogAndWrapCollaboratorPermission

internal/proxy/proxy.go (restBackendCaller.CallTool, case "get_collaborator_permission"):

owner, _ := argsMap["owner"].(string)
repo, _ := argsMap["repo"].(string)
username, _ := argsMap["username"].(string)
if owner == "" || repo == "" || username == "" {
    return nil, fmt.Errorf("get_collaborator_permission: missing owner/repo/username")
}
apiPath = fmt.Sprintf("/repos/%s/%s/collaborators/%s/permission", owner, repo, username)
resp, _ := r.server.forwardToGitHub(ctx, "GET", apiPath, nil, "", enrichmentAuth)
// ... read body, check status, call LogAndWrapCollaboratorPermission

Impact Analysis

  • Maintainability: Changes to error messages, path format, or response handling must be made in two places
  • Bug Risk: The two implementations already diverge slightly (server uses http.DefaultClient with explicit header setup; proxy uses forwardToGitHub). A bug fix applied to one may not be applied to the other.
  • Code Bloat: ~35 lines duplicated

Refactoring Recommendations

  1. Extract a CollaboratorPermissionArgs parser to internal/mcp

    • Add a helper ParseCollaboratorPermissionArgs(args interface{}) (owner, repo, username string, err error) to internal/mcp/collaborator_permission.go
    • This already contains LogAndWrapCollaboratorPermission, making it the natural home
  2. Extract a GitHub REST helper to internal/github (new package) or internal/mcp

    • Create FetchCollaboratorPermission(ctx, httpClient, apiURL, token, owner, repo, username) ([]byte, int, error) to unify the HTTP call
    • Both server and proxy can use this shared function
  3. Estimated effort: 1–2 hours

Implementation Checklist

  • Add ParseCollaboratorPermissionArgs to internal/mcp/collaborator_permission.go
  • Extract the HTTP call to a shared helper (e.g., internal/mcp or internal/httputil)
  • Update internal/server/unified.go:callCollaboratorPermission to use shared helpers
  • Update internal/proxy/proxy.go:restBackendCaller.CallTool to use shared helpers
  • Run make test-all to verify no regressions

Parent Issue

See parent analysis report: #4458
Related to #4458

Generated by Duplicate Code Detector · ● 3M ·

  • expires on May 1, 2026, 6:15 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