OpenHands / OpenHands/software-agent-sdk

[Bug]: Switching to GPT-5.6 fails when history contains colon-delimited tool-call IDs

Open
#4,578 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug llm mcp priority:medium ready-for-dev
Dominant language
Python
Stars
1.1k
Forks
539
Avg merge
1d 19h
Merged PRs (30d)
137

Description

Is there an existing issue for the same bug?
  • I have searched existing issues and this is not a duplicate.
Bug Description

Switching an existing conversation from Kimi K3 to GPT-5.6 fails when the conversation history contains parallel MCP tool calls whose persisted tool_call_id values contain colons.

In a controlled Agent Canvas reproduction, one GitHub MCP call produced the valid ID github_get_file_contents_0. A later turn with two parallel calls produced github_get_file_contents:1 and github_get_file_contents:2. Both calls and observations completed successfully under Kimi.

The reproduction used Agent Canvas's built-in GitHub marketplace integration. In Agent Canvas 1.15.0, that catalog entry uses GitHub's hosted MCP endpoint at https://api.githubcopilot.com/mcp/. In this run, the tool was registered under the github server namespace as github_get_file_contents.

After switching the same conversation to GPT-5.6, the next message failed before the model could respond. The OpenAI Responses API input ID was constructed as fc_github_get_file_contents:1, preserving the colon, which OpenAI rejects.

This appears to be a cross-provider conversation-history normalization problem. Provider-specific or synthesized historical tool-call IDs need to remain valid when replayed through a different provider.

Expected Behavior

Switching LLM profiles should preserve a valid conversation history. OpenHands should either generate provider-portable tool-call IDs initially or deterministically remap historical IDs when converting the history for the destination provider.

Actual Behavior

The conversation enters execution_status=error with a non-retryable LLMBadRequestError. Every subsequent GPT-5.6 turn encounters the same invalid historical ID.

The failure was reproduced against a local SDK-backed agent server started by Agent Canvas with the equivalent explicit SDK command:

uv tool uvx \
  --from openhands-agent-server==1.42.1 \
  --with openhands-sdk==1.42.1 \
  --with openhands-tools==1.42.1 \
  --with openhands-workspace==1.42.1 \
  --with 'agent-client-protocol<0.11' \
  agent-server --host 127.0.0.1 --port 18000

With that SDK server running, the reproducible API sequence is: create a Kimi conversation with GitHub MCP enabled, request two parallel github_get_file_contents calls, switch the conversation to the gpt-5.6 profile, and send one no-tool message. The final command returns normally at the HTTP layer, but the conversation state changes to error and its next persisted event is the OpenAI LLMBadRequestError shown below.

The unsafe cross-provider serialization can also be reproduced without an MCP server, model credentials, or a live conversation:

uv run --with openhands-sdk==1.42.1 python -c 'from openhands.sdk.llm.message import MessageToolCall; t=MessageToolCall(id="github_get_file_contents:1", name="github_get_file_contents", arguments="{}", origin="completion"); print(t.to_responses_dict())'

It emits:

{'type': 'function_call', 'id': 'fc_github_get_file_contents:1', 'call_id': 'github_get_file_contents:1', 'name': 'github_get_file_contents', 'arguments': '{}'}

The generated Responses item id retains the colon and is therefore rejected by OpenAI. This demonstrates the SDK replay defect independently of the path that originally generated the colon-delimited ID.

Steps to Reproduce
  1. Run Agent Canvas with both openhands/kimi-k3 and openhands/gpt-5.6 profiles configured, and install/enable GitHub from the Canvas MCP marketplace. In Agent Canvas 1.15.0, the catalog entry uses https://api.githubcopilot.com/mcp/.

  2. Start a new conversation using Kimi K3.

  3. Send this prompt:

    In a single response, make two GitHub MCP github_get_file_contents calls in parallel: read pyproject.toml and README.md from OpenHands/software-agent-sdk. Do not use any other tool. Then reply only: done.
    
  4. Confirm that the persisted actions and observations have IDs resembling:

    github_get_file_contents:1
    github_get_file_contents:2
    
  5. Switch the same conversation to the GPT-5.6 profile. The reproduction used POST /api/conversations/{conversation_id}/switch_profile with {"profile_name":"gpt-5.6"}; switching from the Agent Canvas model picker should exercise the same conversation switch path.

  6. Send a no-tool message such as Reply only: switched.

  7. Observe the OpenAI 400 and execution_status=error.

Control observation: asking Kimi for exactly one GitHub MCP call generated github_get_file_contents_0, and that turn completed normally. The colon-delimited IDs appeared when two MCP calls were requested in parallel.

Acceptance Criteria
  • A conversation containing parallel MCP calls can switch from Kimi K3 to GPT-5.6 without an invalid-ID error.
  • Every tool-call/function-call ID sent to OpenAI contains only characters allowed by the OpenAI Responses API.
  • Any ID remapping preserves the association between each action and its corresponding observation.
  • Add a regression test covering parallel MCP calls followed by a cross-provider LLM switch.
Installation Method

npm install -g @openhands/agent-canvas

If you selected "Other", please specify

Agent Canvas starts the SDK and agent-server through its bundled uvx command.

SDK Version

Agent Canvas 1.15.0 (current npm release at reproduction time), bundling openhands-agent-server==1.42.1, openhands-sdk==1.42.1, and openhands-tools==1.42.1.

PyPI had SDK/agent-server 1.43.1 available, but this reproduction was not rerun against a standalone 1.43.1 server.

Version Confirmation
  • I have confirmed this bug exists on the LATEST version of OpenHands SDK

It was confirmed on the SDK version bundled by the latest Agent Canvas release, not on standalone SDK 1.43.1.

Python Version

3.12.12

Model Name (if applicable)

Source: openhands/kimi-k3

Destination: openhands/gpt-5.6

Operating System

MacOS

Logs and Error Messages

Persisted actions before the switch:

{"kind":"ActionEvent","tool_name":"github_get_file_contents","tool_call_id":"github_get_file_contents:1"}
{"kind":"ActionEvent","tool_name":"github_get_file_contents","tool_call_id":"github_get_file_contents:2"}

Corresponding observations used the same IDs and completed successfully.

The first GPT-5.6 turn then failed with:

LLMBadRequestError

Invalid 'input[6].id': 'fc_github_get_file_contents:1'. Expected an ID that contains letters, numbers, underscores, or dashes, but this value contained additional characters.
Minimal Code Sample

The reproduction uses the normal Agent Canvas conversation API rather than direct SDK construction:

POST /api/conversations                         # create with Kimi K3 + GitHub MCP
POST /api/conversations/{id}/events             # request two parallel MCP calls
POST /api/conversations/{id}/switch_profile     # {"profile_name":"gpt-5.6"}
POST /api/conversations/{id}/events             # send a no-tool message
Screenshots and Additional Context

The issue was reproduced in a fresh disposable conversation after restarting Agent Canvas. No restart occurred between the Kimi MCP calls and the GPT-5.6 failure.

The important persisted transition was:

Kimi parallel MCP ActionEvent ID: github_get_file_contents:1
OpenAI Responses input item ID:   fc_github_get_file_contents:1

The destination conversion adds fc_ but leaves the provider-invalid colon intact.

A community reproduction also using the Canvas marketplace GitHub integration instead observed get_file_contents_0 and get_file_contents_1. That useful control did not reproduce the colon IDs and exposed a further difference: their tool name was unnamespaced (get_file_contents), while this reproduction persisted the namespaced form (github_get_file_contents). The reason for that naming/ID difference is not yet established and may be configuration-dependent or nondeterministic. The SDK serialization failure above remains deterministic once such an ID is present in conversation history.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with openhands.sdk.llm.message.MessageToolCall.to_responses_dict() and reproduce the shown colon-delimited ID serialization. Trace the conversation switch endpoints and action/observation ID pairing, then add the requested regression coverage for parallel MCP calls followed by a provider switch. Done means the switch succeeds, emitted IDs meet the stated character requirements, and action-to-observation associations remain intact.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend-api-design, testing-qa
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.