Skip to content

[Compatibility] OpenDesign DeepSeek v4.1 streaming tool arguments fail with 502 via combo; opencode-go route works #7920

Open

Description

@amartidandqdq

Summary

OpenTodo's in-app assistant can read/analyze tasks through OpenCodex, but preparing a multi-tool change proposal fails with HTTP 502 when combo/ds41-flash selects OpenDesign/deepseek-v4.1-flash. The recorded upstream error is upstream stream produced malformed tool call arguments.

Changing only the application's configured model to opencode-go/deepseek-v4.1-flash restored the workflow: subsequent real in-app turns completed with proposals containing 15 and 39 changes. The user confirmed a substantial improvement. This isolates a provider/route compatibility problem, not a proven bug in combo selection or OpenTodo.

Client or integration

OpenTodo v0.16.0 in-app assistant, self-hosted Docker on Linux, using an OpenAI-compatible Chat Completions client through a local HTTP reverse proxy to OpenCodex. OpenCodex npm package metadata reports version 2.93.0 (also the latest release checked when reporting).

Provider or upstream service

Failing route: OpenDesign/deepseek-v4.1-flash, adapter openai-chat, reached through a failover combo.
Working alternative: opencode-go/deepseek-v4.1-flash directly.

Redacted combo definition (no credentials):

{
  "ds41-flash": {
    "strategy": "failover",
    "stickyLimit": 1,
    "defaultEffort": "high",
    "reasoningEffortMode": "adaptive",
    "targets": [
      { "provider": "OpenDesign", "model": "deepseek-v4.1-flash", "weight": 1 },
      { "provider": "opencode-go", "model": "deepseek-v4.1-flash", "weight": 1 }
    ]
  }
}

The combo and direct route may resolve different reasoning defaults; this was not controlled, so this is not an identical-upstream/model-parameters A/B test.

Endpoint or capability

POST /v1/chat/completions, streamed tool calls. OpenTodo sends stream: true, stream_options: {"include_usage": true}, tools, tool_choice: "auto", and max_tokens: 8192. It sends tool results back for subsequent rounds.

Current behaviour

Two real proposal turns failed on 2026-10-28. OpenTodo displayed The provider reported an error while answering. No completed proposal was returned for either failed turn.

OpenCodex usage records (selected fields only; no prompts, task data, tokens, account identifiers, or credentials):

[
  {
    "timestamp": 1793212887267,
    "requestedModel": "combo/ds41-flash",
    "status": 502,
    "durationMs": 38308,
    "upstreamError": "upstream stream produced malformed tool call arguments",
    "failureStage": "side-effect",
    "failureCause": "upstream-fault",
    "attempts": [
      {"provider": "OpenDesign", "model": "deepseek-v4.1-flash", "adapter": "openai-chat", "status": 502}
    ]
  },
  {
    "timestamp": 1793213129330,
    "requestedModel": "combo/ds41-flash",
    "status": 502,
    "durationMs": 38594,
    "upstreamError": "upstream stream produced malformed tool call arguments",
    "failureStage": "side-effect",
    "failureCause": "upstream-fault",
    "attempts": [
      {"provider": "OpenDesign", "model": "deepseek-v4.1-flash", "adapter": "openai-chat", "status": 502}
    ]
  }
]

The associated OpenCodex log errors occurred at 2026-10-28T18:42:05.574Z and 2026-10-28T18:46:07.923Z.

Associated OpenTodo telemetry:

18:42:05Z failed=true  proposed=0 input_tokens=34700 output_tokens=887
18:46:07Z failed=true  proposed=0 input_tokens=7965  output_tokens=195

Expected behaviour

For valid upstream tool-call streams, relay complete tool names and valid JSON arguments so OpenTodo can construct a reviewable proposal. If the upstream really generates invalid arguments, fail explicitly rather than inventing arguments or silently dropping requested changes.

Please help determine whether this failure is caused by invalid upstream output, streamed delta assembly/normalization, truncation, or another provider-specific incompatibility. A sanitized indication of the invalid argument shape/parse failure would make diagnosis easier.

Only one attempt was recorded for each failed combo request. This is an observation, not a request for unconditional failover: failureStage=side-effect may correctly prevent replay after tool-call events were already delivered. Please preserve that safety boundary.

Minimal redacted request or reproduction

Observed workflow, not yet a deterministic standalone reproduction:

  1. Configure the failover combo above on OpenCodex 2.93.0.
  2. Point OpenTodo v0.16.0's compatible assistant at OpenCodex and set its model to combo/ds41-flash.
  3. With multiple projects and open tasks, ask the assistant to analyze the projects. Read-only analysis succeeded.
  4. Ask it to prepare the proposed organizational changes (move a task to its project, add dependencies, schedule tasks). Two attempts failed as recorded above.
  5. Keep the application, endpoint and data unchanged; change its model to opencode-go/deepseek-v4.1-flash. Subsequent turns successfully returned proposals.

The original task contents and browser conversation are personal and are deliberately not attached. No raw upstream SSE capture was retained. The exact malformed JSON payload is therefore unavailable, and the failure has not been reproduced with synthetic data.

Tests and workaround

  • OpenTodo's saved-settings connection/tool-definition test passed for the direct opencode-go model.
  • A separate synthetic streaming test with tool_choice: "auto" returned the requested tool with valid JSON arguments through the direct model; no real task tools were executed by that test.
  • Actual OpenTodo turns after the change completed with proposed=15, failed=false at 18:54:40Z and proposed=39, failed=false at 18:56:44Z.
  • Direct-route requests in OpenCodex usage records returned HTTP 200 around those turns.
  • An earlier direct-route turn did hit OpenTodo's output-token limit. The workaround fixes the observed tool-stream failure, but does not remove output-budget limits or guarantee all future tool calls succeed.
  • NOT RUN: deterministic synthetic reproduction of the OpenDesign failure; raw SSE comparison; adapter regression suite. No OpenCodex code was changed.

Upstream documentation

Related issues

#1472 and #1473 describe older malformed DeepSeek tool-call failures, but concern deepseek-v4-flash-free on OpenCodex 2.12.0 rather than the OpenDesign v4.1 route here. #1473 was automatically closed for insufficient detail, not a confirmed resolution. No matching open OpenDesign/DeepSeek issue was found in the search performed for this report.

Checks

  • I searched existing provider and compatibility issues.
  • The request and response were redacted.
  • Expected behaviour is based on the concrete client's streaming tool-call requirements.

Activity

  1. coderabbitai commented on Oct 28, 2026

    @coderabbitai
    Contributor
    🔗 Related PRs

    #7474 - [WRONG BRANCH] release: promote dev 845ac6b to main as 2.90.0 [merged]
    #7849 - fix(openai-chat): keep captured usage on an invalid negotiated completion [open]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. added
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    streamingSSE, WebSocket, terminal stream frames
    toolstool_calls, MCP, web-search / sidecar tools
    on Oct 28, 2026
  3. Ingwannu commented on Oct 29, 2026

    @Ingwannu
    Owner

    This is a useful bounded report. The observed single attempt plus failureStage: side-effect is compatible with refusing an unsafe resend after tool-call events have already been delivered; switching providers is a workaround, not proof that the combo selector caused the malformed arguments. I will keep the replay boundary intact.

    To distinguish invalid upstream JSON from delta assembly/truncation, the next useful artifact is a synthetic-data tool fixture: one dummy tool/schema, the ordered tool-call index/id/name and argument fragments for that dummy call, the terminal/finish reason, whether the connection reached clean EOF, and the resulting valid/invalid argument shape. Replace ids with stable fixture labels and use invented values; do not attach the original personal task arguments or raw conversation. Also record the effective model/reasoning/output limit on both compared routes, since those were not held equal. An empty final fragment, an object where a JSON string is expected, concatenated full snapshots and a truncated JSON string are distinct cases.

    Related #7849 is now merged and preserves captured usage on invalid negotiated completion; it does not repair malformed JSON or establish the OpenDesign incident as fixed. Keeping this issue open pending a deterministic adapter fixture. No live provider call, tool execution or automatic retry was performed.

  4. lidge-jun commented on Oct 30, 2026

    @lidge-jun
    Owner

    Maintainer triage (2026-10-30, against dev 3a70dd4788):

    The 502 comes from the shared stream encoder's final check on assembled tool arguments. The OpenAI Chat adapter appends each tool_calls[].function.arguments fragment to the pending call matched by index or id (src/adapters/openai-chat.ts around the call.args += rawArguments accumulation) and flushes on finish_reason; the encoder (src/protocols/encoders/adapter-events.ts) then refuses arguments that are not empty and do not JSON.parse, which produces upstream stream produced malformed tool call arguments. Because the failure is declared after the call may already be visible, the combo correctly records failureStage: side-effect and does not retry.

    What the log cannot tell us is why the buffer was not valid JSON: the upstream may have sent incomplete JSON, the stream may have been cut, the provider may resend the full argument snapshot in each delta (which would duplicate text when accumulated), or two parallel calls may have been joined under one index. Each of those needs a different fix, and changing the shared accumulation rule on a guess would risk every other Chat provider.

    To move this forward, could you capture one failing exchange from OpenDesign deepseek-v4.1-flash with a harmless synthetic tool (no private data): the raw SSE data: lines for the tool call (index, id, name and every arguments fragment in order) plus the final chunk with finish_reason? ocx request history or a curl -N against the OpenDesign endpoint with the same request shape both work. With that we can tell an upstream defect from an assembly bug and add a fixture-based regression test.

    Labelling this as needing information; it stays open.

  5. added
    needs-infoWaiting on reporter for a concrete spec or reproduction
    on Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-infoWaiting on reporter for a concrete spec or reproductionproviderProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reportsstreamingSSE, WebSocket, terminal stream framestoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions