Skip to content

<go-sdk> Return failed tool result for empty tool name to avoid dangling tool_calls #1540

@lonegunmanb

Description

@lonegunmanb

Summary

When a model emits an OpenAI-compatible tool call with a missing/empty tool name, the current SDK/CLI path appears to surface it as a session-level error instead of returning a structured failed tool result for the pending tool call. This can leave the provider-visible conversation history with an assistant.tool_calls entry that is not followed by a corresponding tool message, causing the next request in the same session to be rejected by OpenAI-compatible providers.

Observed behavior

In a long-running session using an OpenAI-compatible provider, the model emitted a malformed tool call whose function/tool name was empty. The host surfaced:

Tool name is missing

The application caught that session/stream error and attempted to continue the same session by sending a corrective nudge. The next provider request then failed with:

400 An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. (insufficient tool messages following tool_calls message)

This suggests the malformed assistant tool call had already become part of the session history, but no structured tool result message was appended for its tool_call_id.

Why this is hard for SDK consumers to repair

At the application layer we only receive a session error message such as Tool name is missing. We do not have a reliable way to append a provider-visible structured tool result message with the original tool_call_id, nor can we safely edit/rewind the SDK-managed conversation history.

Sending a normal follow-up/nudge prompt is not equivalent to appending a role=tool message. OpenAI-compatible providers validate the message structure before generating the next response, so a textual nudge cannot repair a dangling assistant.tool_calls entry.

Requested change

If the tool call has a valid tool_call_id but the tool name is missing/empty, please consider returning a structured failed tool result for that tool call instead of surfacing a session-level/JSON-RPC error that leaves history unrepaired.

For example, conceptually:

if req.ToolCallID != "" && req.ToolName == "" {
    return &toolCallResponseV2{Result: ToolResult{
        TextResultForLLM: "Tool call failed: tool name is missing or incorrect. Retry using one of the registered tool names.",
        ResultType:       "failure",
        Error:            "tool name is missing or incorrect",
        ToolTelemetry:    map[string]any{},
    }}, nil
}

The important behavior is that the session/provider history gets a proper failed tool result associated with the pending tool_call_id, allowing the model to self-correct within the same session and preserving long-running context.

Edge cases

If the tool_call_id is also missing, then a standards-compliant tool message cannot be constructed. In that case, the SDK/CLI may need a different recovery path, such as discarding/rolling back the invalid assistant tool-call message before the next provider request, or exposing enough structured diagnostic data for clients to decide whether to reset the session.

Expected behavior

  • Empty/missing tool name with a valid tool_call_id should produce a structured failed tool result, not poison the session history.
  • The next request in the same session should not fail provider-side with dangling assistant.tool_calls / missing tool messages.
  • SDK consumers should not need to rebuild the session and lose long-running context just to recover from one malformed tool call.

Environment

  • SDK: Go SDK path in github.com/github/copilot-sdk/go API surface
  • Provider mode: OpenAI-compatible completions/chat tool-call protocol
  • Provider observed: DeepSeek OpenAI-compatible API

Metadata

Metadata

Assignees

No one assigned

    Labels

    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