Skip to content

[duplicate-code] Duplicate Code Pattern: Backend Request Execution in Prompt Handler #8122

Closed

Description

@github-actions

Part of duplicate code analysis: #8121

Summary

The prompt handler closure introduced in internal/server/tool_registry.go by the prompt passthrough feature duplicates the exact same pattern that executeBackendToolCall in internal/server/unified.go was created to centralize. Each prompt registered by registerPromptsFromBackend independently reimplements: get connection → send backend request → check RPC error → unmarshal result.

Duplication Details

Pattern: Backend Request Execution (connect → send → error-check → unmarshal)

  • Severity: Medium
  • Occurrences: 2 (+ the established executeBackendToolCall helper that was meant to own this)
  • Locations:
    • internal/server/tool_registry.go (lines 415–439): prompt handler closure inside registerPromptsFromBackend
    • internal/server/unified.go (lines 214–241): executeBackendToolCall function (existing, purpose-built helper)

Prompt handler (new, duplicated pattern):

sessionID := us.getSessionID(ctx)
backendConn, connErr := launcher.GetOrLaunchForSession(us.launcher, serverIDCopy, sessionID)
if connErr != nil {
    return nil, fmt.Errorf("failed to connect to backend %s: %w", serverIDCopy, connErr)
}
params := map[string]interface{}{
    "name":      promptNameCopy,
    "arguments": req.Params.Arguments,
}
response, reqErr := backendConn.SendRequestWithServerID(ctx, "prompts/get", params, serverIDCopy)
if reqErr != nil {
    return nil, reqErr
}
if response.Error != nil {
    return nil, fmt.Errorf("backend error for prompts/get %s/%s: code=%d, message=%s",
        serverIDCopy, promptNameCopy, response.Error.Code, response.Error.Message)
}
var promptResult sdk.GetPromptResult
if unmarshErr := json.Unmarshal(response.Result, &promptResult); unmarshErr != nil {
    return nil, fmt.Errorf("failed to parse prompt result: %w", unmarshErr)
}
return &promptResult, nil

executeBackendToolCall (existing, centralizing helper):

conn, err := launcher.GetOrLaunchForSession(l, serverID, sessionID)
if err != nil {
    return nil, fmt.Errorf("failed to connect: %w", err)
}
response, err := conn.SendRequestWithServerID(ctx, "tools/call", map[string]interface{}{...}, serverID)
if err != nil {
    return nil, err
}
if response.Error != nil {
    return nil, fmt.Errorf("backend error: code=%d, message=%s", response.Error.Code, response.Error.Message)
}
var result interface{}
if err := json.Unmarshal(response.Result, &result); err != nil {
    return nil, fmt.Errorf("failed to parse result: %w", err)
}
return result, nil

Impact Analysis

  • Maintainability: Error handling and connection logic are now in two separate places. A fix (e.g., improved error messages, retry logic, logging) applied to executeBackendToolCall will not be applied to the prompt handler.
  • Bug Risk: The two implementations already diverge: executeBackendToolCall logs code=%d details before returning; the prompt handler does not. Future divergence is likely.
  • Code Bloat: Each new backend capability type (e.g., resources/read) added following the prompt handler pattern will add ~20 lines of duplicated backend-call boilerplate.

Refactoring Recommendations

  1. Extract a generic executeBackendRequest[T any] helper in internal/server/unified.go:

    // executeBackendRequest sends a JSON-RPC request to a backend and unmarshals the result
    // into T. It centralizes: GetOrLaunchForSession → SendRequestWithServerID → error check → unmarshal.
    func executeBackendRequest[T any](ctx context.Context, l *launcher.Launcher, serverID, sessionID, method string, params map[string]interface{}) (T, error) {
        var zero T
        conn, err := launcher.GetOrLaunchForSession(l, serverID, sessionID)
        if err != nil {
            return zero, fmt.Errorf("failed to connect to backend %s: %w", serverID, err)
        }
        response, err := conn.SendRequestWithServerID(ctx, method, params, serverID)
        if err != nil {
            return zero, err
        }
        if response.Error != nil {
            return zero, fmt.Errorf("backend error %s: code=%d, message=%s", method, response.Error.Code, response.Error.Message)
        }
        var result T
        if err := json.Unmarshal(response.Result, &result); err != nil {
            return zero, fmt.Errorf("failed to parse %s result: %w", method, err)
        }
        return result, nil
    }
  2. Refactor executeBackendToolCall to delegate to the new helper (preserving the existing public API).

  3. Update the prompt handler in registerPromptsFromBackend to call:

    result, err := executeBackendRequest[sdk.GetPromptResult](ctx, us.launcher, serverIDCopy, sessionID, "prompts/get", params)

Implementation Checklist

  • Add executeBackendRequest[T any] generic helper in internal/server/unified.go
  • Refactor executeBackendToolCall to delegate to executeBackendRequest
  • Update prompt handler in registerPromptsFromBackend to use executeBackendRequest
  • Verify tests pass: make test
  • Confirm no behaviour change for existing tool and prompt calls

Parent Issue

See parent analysis report: #8121
Related to #8121

Generated by Duplicate Code Detector · 698.6 AIC · ⊞ 35.7K · ◷

  • expires on Jul 3, 2026, 3:44 AM UTC

Activity

  1. github-actions commented on Jun 27, 2026

    @github-actions
    ContributorAuthor

    🤖 This is an automated response from Repo Assist.

    Verified. The prompt handler closure in tool_registry.go (lines 415–439) does indeed duplicate the connect→send→error-check→unmarshal pattern that executeBackendToolCall in unified.go was built to own.

    One divergence already present: executeBackendToolCall logs code=%d details before returning the error; the prompt handler constructs a more descriptive message (backend error for prompts/get %s/%s) but omits the structured log call. Any future error-handling improvements (e.g., retries, timeout context) applied to executeBackendToolCall would need to be separately applied to the prompt handler.

    The generic executeBackendRequest[T any] helper is a sound approach for Go 1.21+. The constraint T doesn't need to be bounded beyond what json.Unmarshal accepts, so any works fine. The main integration question is whether executeBackendToolCall itself can be refactored to delegate to executeBackendRequest cleanly — it currently has some logging that would need to be preserved.

    Happy to implement this if it would help move things forward.

    Generated by Repo Assist · 135.8 AIC · ⊞ 12.2K · ◷
    Comment /repo-assist to run again

    Add this agentic workflows to your repo

    To install this agentic workflow, run

    gh aw add githubnext/agentics/workflows/repo-assist.md@851905c06e905bf362a9f6cc54f912e3df747d55
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions