Severity: High — our app’s primary interaction (a widget button) silently does nothing on the default model.
App: Custom MCP connector (unpublished, developer mode)
Model: gpt-5-5 (Instant) | Plan: team | Client: ChatGPT web, macOS desktop browser | Cluster: australiaeast | Date: 2026-08-03 (UTC+5:30)
Summary
When our widget sends a follow-up prompt via window.openai.sendFollowUpMessage, the resulting turn is not classified as a connector turn and the connector toolset is never attached. The model cannot perform the api_tool.list_resources → api_tool.call_tool sequence, so the tool call the message asks for never happens (server_ste_metadata: tool_invoked=false, turn_use_case=“multimodal”). An identical request typed by the user in the same conversation seconds later is classified turn_use_case=“connectors”, performs discovery, and succeeds.
The failure is content-independent: we tested two wordings at opposite extremes — plain natural language (“Identify the font in region 2 of my image (box: {…}).”) and an explicit imperative (“SYSTEM EVENT: … MANDATORY: call the predict_font tool from this app now …”). Identical outcome. In one capture the model itself streamed, user-visibly:
“I need to call the font identification tool for the selected region before I can answer, but I can’t complete that tool call from the current state because the required plugin action wasn’t executed in this interface.” — followed by the exact arguments it would have used, including the raw sediment://file_… id leaked into user-visible text.
Reproduction
- User uploads an image and asks a question about it → our connector’s first tool (detect_font_regions) runs (works; current-turn attachment), and our widget renders with selectable regions.
- User clicks the widget’s action button → widget calls sendFollowUpMessage. Message arrives as role:“tool” (name ui://widget/wtf-crop_html) with metadata chatgpt_sdk_followup_prompt:true, is_visually_hidden_from_conversation:true.
- Actual: no tool discovery, no tool call, zero requests reach our MCP server. Observed responses: plain text answer; a completely EMPTY final message (parts:[“”]); or the self-report quoted above.
- Control: user types the same request as a normal message → api_tool.list_resources → api_tool.call_tool (predict_font) → our server 200, results render. Works every time.
Evidence — reference IDs
Conversation A: 6a709408-8660-83ee-983a-b79c678da087
- Failing widget turn (natural language): request_id 375571ca-aaf9-446c-8739-3c60c9cdd161, turn_trace_id fb459657-e81e-46ef-89f7-16fc38ad7796 → tool_invoked=false, turn_use_case=multimodal
- Succeeding typed turn (same region, ~4 min later): request_id 26de71cf-364f-467c-b03b-75f27e96b015, turn_trace_id 93fd2b4d-2399-4843-9202-5ed1c19baae0 → tool_name=ApiToolWrapper, tool_invoked=true, turn_use_case=connectors
Conversation B: 6a70a542-b394-83ee-965d-a260003da752
- Failing widget turn 1 (imperative wording, EMPTY reply): request_id 4e8dae6e-4573-4126-a4ee-3266a09f121a, turn_trace_id bfc8dbbb-b542-4b7c-b6b2-99a3409d6550
- Failing widget turn 2 (imperative wording, self-report + file_id leak): request_id 3d810e87-5936-44df-b171-93c8e0e7d586, turn_trace_id 6e9126ee-904b-4d21-b969-6a3cec755705
App attribution ids: asdk_app_6a674256128c8191893f73dbbf3e14d5 (conv A), asdk_app_6a70a50d648c8191b637367a7d4966e8 (conv B). Failing turns also show rebase_system_message/rebase_developer_message system events at turn start. Full request payloads and SSE streams for all five turns available on request.
Likely related community reports: 1382389 (tool calls only fire when app explicitly selected), 1367100 (sendFollowUpMessage → text-only replies), 1384922 (tools lost after one call).
Impact
The widget’s action button — the core interaction of our connector — silently fails on the default model. Users click and get nothing (empty response) or prose instead of results; workarounds (typing the request, re-selecting the app via “+”) defeat the widget UX entirely. The app cannot detect the failure: sendFollowUpMessage resolves successfully, the turn completes “successfully”, and nothing reaches our server. Internal identifiers (sediment:// file ids) leak into user-visible text when the model flails.
Asks
- Classify widget-originated follow-up prompts (chatgpt_sdk_followup_prompt:true) as connector turns for the originating app (chatgpt_sdk_attribution identifies it), or otherwise guarantee the app’s tools are attached/discoverable in that turn.
- Provide an app-detectable signal when a follow-up prompt’s turn runs without the app’s tools, so widgets can fall back gracefully.
- Is there any work around and best practices for this