Skip to content

tool calls: arguments corrupt across calls and schema-invalid calls can execute #47902

Open

Description

@zhiganov

Summary

During one OpenCode V2 session, tool arguments became corrupted across multiple consecutive assistant turns. A patch argument literally ended with internal-looking serialization markers (<|DELIM_AE|>step_type...); other calls were truncated, contained injected words, mixed fields from another tool schema, or produced malformed paths. A later corrupted read call requested permission for an unrelated path under C:\Users\Harry\....

Patch validation rejected the malformed patch requests. The user declined the unexpected read permission; the durable tool record says executed: false and The user declined this tool call. The incident was alarming because corrupted path fragments then also appeared in ordinary assistant text.

Environment

  • opencode version: opencode2 v0.0.0-beta-19296
  • OS: Microsoft Windows [Version 10.0.26200.9168] (win32 x64)
  • Terminal: Windows Terminal (WT_SESSION set); TERM=xterm-256color; TERM_PROGRAM and COLORTERM unset
  • Shell: C:\WINDOWS\system32\cmd.exe (ComSpec); tool processes were spawned with PowerShell 7
  • Install/channel: installed V2 beta (local=false in CLI log). A separate legacy opencode 1.18.16 executable is also on PATH but was not the client in use.
  • Active plugins: none configured in the effective /api/config; no local plugin files found in the workspace or global OpenCode config plugin directories
  • Provider/model: native openai/gpt-5.6-sol, variant high
  • Shared service after the incident: healthy on the same beta version; service run ID associated with the parent activity was 6fed79f9

Reproduction

The failure was repeated many times within one session but has not been reproduced deterministically in a fresh session.

  1. Use a long OpenCode V2 session with openai/gpt-5.6-sol (high) on Windows.
  2. Ask the assistant to make a sequence of repository edits using the patch tool.
  3. Observe consecutive tool calls around 2026-09-08T06:13Z–06:23Z.
  4. Inspect persisted session messages/tool state. Some patch calls are valid, while others have truncated or corrupted arguments despite the assistant messages finishing with finish: "tool-calls".
  5. Continue the conversation. In this incident, corruption progressed from patch text/path arguments to an unrelated read path and then malformed path fragments in ordinary assistant text.

Captured session: ses_f99489a00ffest1oZ62oS41GuV.

Expected Behavior

Tool arguments should contain only the model's intended, schema-valid payload. Internal serialization markers must never appear in tool input. Fields must not migrate between tool schemas, and malformed generated paths must not trigger unrelated external-filesystem permission prompts.

If provider output is corrupted, OpenCode should classify and stop/retry it before presenting a filesystem permission request, while preserving enough safe diagnostics to identify whether corruption occurred at the provider stream, adapter, or message-serialization boundary.

Actual Behavior

At 2026-09-08T06:17:09.471Z, a persisted patchText ended exactly as follows (project content before the marker redacted):

[redacted patch content]<|DELIM_AE|>step_type<|DELIM_AE|>step_content<|DELIM_AE|>content_hash<|DELIM_AE|>provenance
<|DELIM_AE|>

The patch validator rejected it because the last line was not *** End Patch. Durable IDs:

message: msg_07fa9ab76001IDBvD254anx86P
part:    call_LeagPElcnVkyxYkwwZJchbl1
provider item: fc_0f4c75c33f7cbbf4016a9fa867311087d2b94a22ff1785a53a

Another corrupted patch call had input fields from both patch and grep:

{
  "fields": ["patchText", "path", "include", "caseSensitive", "limit"],
  "patchTextEndedWithEndPatch": false
}

Its error was:

patch verification failed: The last line of the patch must be '*** End Patch'

Other exact validation/runtime symptoms in the same cluster included:

Invalid hunk at line 38: Update file hunk for path 'C:\_{' is empty
Update file hunk for path 'book-pencil55? no' is empty
Working directory does not exist: C:\Users\[user]\opencode\ Eich
Failed to read file to update C:\Users\[user]\opencode\book-power outdoors: file does not exist
Invalid arguments for tool "shell": command: Missing key
Arguments provided: { "path": "C:\Users\[user]\opencode\bookHeld?", "offset": 1, "limit": 2 }

Patch content also acquired unrelated suffixes such as Nancy?, stereotrippinovoices??, and feder249.

Across the roughly ten-minute cluster, persisted tool state contains:

  • 37 patch calls: 14 completed and 23 rejected
  • 15 of the 23 rejected patches were missing the required final *** End Patch
  • 8 had other malformed hunks, injected text, or malformed paths
  • 2 malformed shell calls
  • 1 unexpected read call

At 2026-09-08T06:22:43.564Z, the assistant emitted this read input:

C:\Users\Harry\oplish\FOP?lify?Invalid path? no.

Durable state:

message:  msg_07faec16e0015T8TAQGX7uNCzk
part:     call_w2j92ZXzxvfyMDWZZViZBDIP
tool:     read
status:   error
executed: false
error:    The user declined this tool call

The assistant subsequently emitted malformed path fragments in normal text, including C:\ibele\Users\Harry\... and C:\Users\[user]\abatement\opSED? Wait—this....

Additional Context

  • This appears related to fix(llm): handle truncated OpenAI tool arguments #36766, but is materially different from a tool argument merely ending mid-JSON. Here the tool object parsed and reached validation, while its string content contained internal-looking delimiter names; other calls mixed schema fields or inserted unrelated words and paths.
  • No query for DELIM_AE, "step_type" "step_content", tool-payload corruption, or malformed/truncated tool arguments found an issue covering this exact delimiter leak plus cross-call corruption.
  • No WARN or ERROR lines matched the parent session/run in the normal OpenCode log during the incident window. The delimiter was found in the persisted session message/tool input, not the log.
  • The parent session was long (about 182k–192k cached-input tokens around the incident), but the affected messages had ordinary nonzero output token counts and finish: "tool-calls"; there was no recorded finish: "length" on the cited calls.
  • Valid patch calls in the same cluster did execute. Existing user edits were intentionally left untouched; this report does not claim every call was corrupt.
  • Cause is not established. The evidence is consistent with provider/adapter/message-serialization corruption, but does not by itself identify which boundary introduced it.
  • No service restart, destructive database operation, or fresh dependency/build action was performed during diagnosis.

Activity

  1. opencode-agent commented on Sep 8, 2026

    @opencode-agent
    Contributor

    Could you attach the sanitized OpenCode logs covering the incident window (2026-09-08T06:13Z–06:23Z), including all lines for the relevant server run ID rather than only WARN/ERROR lines? The installed-build log is at ~/.local/share/opencode/log/opencode.log (on Windows, under your user profile). Please redact API keys, authorization headers, prompts, file contents, and other sensitive data before uploading.

    If you have a session export for ses_f99489a00ffest1oZ62oS41GuV, please attach that too after the same redaction.

  2. zhiganov commented on Sep 8, 2026

    @zhiganov
    Author

    Thanks — I prepared and reviewed both requested artifacts. They are ready to attach below:

    • opencode-2026-09-08T0613Z-0623Z-run-6fed79f9-sanitized.log
    • session-sanitized.json

    Sanitization and integrity notes:

    • The log contains 150 lines. Every line is from server run 6fed79f9 and falls within 2026-09-08T06:13Z–06:23Z; process arguments, working directories, and executable paths are redacted.
    • The session export was first generated with opencode2 export --sanitize ses_f99489a00ffest1oZ62oS41GuV, then passed through a second sanitizer because 73 nested tool-error messages still contained paths, project names, command arguments, or expected-content excerpts.
    • The reviewed export remains valid JSON with all 2,346 messages. A projection of diagnostic metadata (timestamps, IDs, model/provider, token counts, tool names/statuses, and provider response IDs) is identical before and after the second pass.
    • Explicit scans of both final files returned zero matches for absolute Windows/UNC/home/temp paths, known usernames or project names, emails, credentials and sensitive headers, private keys, URLs, source filenames, shell command flags, and known malformed-content markers.

    Additional occurrences after the original report:

    • At 2026-09-08T12:23:50.518Z, the durable export records a shell tool error (msg_080f96d60001BjDLpDzSX5NzLa, call_gRVqqgUyio75Gq92e9IZAHcJ) after its workdir was corrupted to C:\Users\[user]\opencode\beautifulbots? No.. The public export retains the IDs, timestamp, tool, and error status while replacing the path-bearing error message with a categorical placeholder.
    • During the subsequent attachment-diagnostic task, a patch payload intended to create a second-pass sanitizer was observed with malformed internal-looking text, and the immediately following pattern-scan call was also malformed. I am not quoting or interpreting those untrusted fragments here beyond recording the two consecutive malformed calls.

    These artifacts do not establish malware or identify whether the provider, adapter, or message-serialization boundary introduced the corruption.

    SHA-256:

    • Log: 52F222C8E268B0CF6A3B059E79AFCE50DA60482D6015E9FE03CFC492F0EAFFAC
    • Session export: 780B33E374607851017F82CDE9CDA7B60016D3A5C24C67B29470C79361095762

    opencode-2026-09-08T0613Z-0623Z-run-6fed79f9-sanitized.log
    session-sanitized.json

  3. zhiganov commented on Sep 9, 2026

    @zhiganov
    Author

    A second occurrence on v0.0.0-beta-19378 adds one material finding:

    OpenCode accepted and executed parseable tool calls that contained a valid required argument plus unknown JSON properties. Four affected shell calls completed with exit code 0. One performed an external GitHub mutation; the resulting corrupt text was subsequently repaired.

    The persisted tool state contains the unknown properties before execution, and the process-spawn log shows the corresponding already-corrupted PowerShell command. This strengthens the need for strict argument validation: unknown properties should reject the entire call rather than allowing the valid portion to execute.

    No private repository names, project content, credentials, or raw command bodies are included here.

  4. changed the title [-]llm: tool arguments leak DELIM_AE markers and corrupt across calls[/-] [+]tool calls: arguments corrupt across calls and schema-invalid calls can execute[/+] on Sep 9, 2026
  5. DovydasNavickas commented on Sep 12, 2026

    @DovydasNavickas

    Got very similar situations in multiple projects while using opencode2 in all of them with GPT-5.6 High, e.g.:
    cd ~/Projects/myproject CH? Wait path. Must exact. && rtk git diff --cached --unified=20 -- state/main.yml
    In another one:
    rtk git show a0aed8d2^:projects/Bookkeepingтобыchito pits? no.
    Those are Cyrillic characters right after the path excerpt 🙊 Could this be coming from some sort of prompt injection? Or is it just Sol generating random stuff?

    P.S. This is happening on Linux, so this is not Windows specific.

  6. chenhz01 commented on Sep 17, 2026

    @chenhz01

    The delimiter leak (<|DELIM_AE|>) plus cross-schema field migration is a nasty capture, and the durable tool records make this report unusually strong — having executed: false on the dangerous read call is exactly the kind of evidence this class of bug usually lacks.

    From our side: we enforce schema validation before execution on every tool call, and two invariants caught our own corruption class early:

    1. Fields must not migrate between tool schemas. Validate against the called tool's schema and reject unknown fields rather than best-effort coercing them — your patch+grep field mix (patchText alongside include/caseSensitive/limit) fails this immediately and loudly.
    2. Internal serialization markers must never appear in tool input. A denylist scan on arguments costs microseconds and would have caught this at the adapter boundary instead of surfacing as a filesystem permission prompt for a path under an unrelated user profile.

    Agree with the report's framing: classify-and-stop at the provider/adapter/message-serialization boundary beats letting corrupted arguments reach permission gating — a declined permission is a near-miss, not a defense.

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