Skip to content

core: malformed tool input remains unresolved until the next prompt #36421

Closed

Description

@kitlangton

Summary

In V2, malformed streamed tool JSON can fail the assistant step while leaving the started tool unresolved. The next user prompt then repairs the stale tool as interrupted, so the transcript shows both the original provider-invalid-output error and a delayed Tool execution interrupted error for the same call.

Environment

  • opencode version: 0.0.0-next-15329 (the long-lived Session was originally created under next-15293)
  • OS: macOS 25.5.0 (arm64)
  • Terminal: Zed 1.10.0+stable.318.111c4082fd38215c31fa87803ece7695f898a94e
  • Shell: /bin/zsh
  • Install/channel: next
  • Active plugins: global share-with-team.ts and @warp-dot-dev/opencode-warp; project-specific plugin status was not relevant to the failure

Reproduction

  1. Run a V2 session through the native OpenAI Responses route.
  2. Let the provider start a tool call and stream arguments that end as invalid JSON before the call can be parsed.
  3. Observe the step fail with provider.invalid-output.
  4. Send another user prompt to resume the Session.

The provider-dependent malformed output is intermittent, but the captured durable sequence was:

14:34:12  session.tool.input.started  tool=patch
14:34:22  session.tool.input.ended    bytes=2522, invalid JSON ending mid-string
14:34:22  session.step.failed         provider.invalid-output
14:34:22  session.execution.failed    provider.invalid-output
14:35:03  session.input.admitted      text=continue
14:35:03  session.execution.started
14:35:03  session.tool.failed         aborted: Tool execution interrupted: patch
14:35:03  session.input.promoted

Expected Behavior

The malformed JSON should remain the truthful provider failure. Any tool created by session.tool.input.started should be settled as failed in that same terminal step, before session.step.failed / session.execution.failed completes.

Resuming the Session should not discover and publish a second delayed interruption for the already-failed attempt.

Actual Behavior

The parser correctly rejected the 2,522-byte argument string:

Invalid JSON input for openai-responses tool call patch

The raw argument string ended mid-string, so this error was valid and the patch tool was never invoked. However, the projected tool remained in a streaming state after the step and execution failed. On the next prompt, failInterruptedTools found it and emitted:

Tool execution interrupted: patch

The TUI consequently rendered a Patch failed block plus the provider-invalid-output error, making one malformed provider call look like two independent failures.

Additional Context

This is adjacent to #27871 and #33687, but the captured occurrence is on the V2 event model and has a narrower invariant: every session.tool.input.started record must receive a terminal tool event before the owning step reaches terminal failure.

Relevant current code:

  • packages/llm/src/protocols/shared.ts: parseToolInput correctly classifies malformed JSON.
  • packages/core/src/session/runner/llm.ts: terminal stream handling calls failUnsettledTools, while the next drain also runs failInterruptedTools over projected streaming/running tools.
  • packages/core/src/session/runner/publish-llm-event.ts: tool input start creates an unsettled tool; input end closes the raw fragment but does not settle the tool.

A regression test should emit tool-input-start, partial input, and an InvalidProviderOutput failure, then assert that the same attempt durably emits session.tool.failed before session.step.failed, and that a subsequent resume emits no repair failure for that call.

Activity

  1. self-assigned this
    on Jul 12, 2026
  2. opencode-agent commented on Jul 22, 2026

    @opencode-agent
    Contributor

    Addressed by #36477. Malformed tool input now settles the tool part during the failing execution instead of leaving it unresolved until another prompt arrives, with the failure represented durably. Closing as resolved on the v2 branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions