anomalyco / anomalyco/opencode

ACP: `opencode acp` fails to parse Content-Length framing on stdio — empty initialize response, agents see empty turns

Open
#48,766 0 comments 0 reactions 1 assignee View on GitHub

@jlongster is already working on this.

Since Sep 13, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

What happened

opencode acp (stdio, Agent Client Protocol) fails to parse the very first JSON-RPC message (initialize) when it is framed with the standard Content-Length header used by ACP/LSP over stdio. The server logs a parse error to stderr and never answers; the client sees an empty stream / hang, and ACP clients end up with "empty agent turns".

Environment
  • opencode: 1.18.29
  • installed via: standalone binary at /usr/bin/opencode (bun bundle)
  • OS: CachyOS (Arch-based), Linux 7.2.3
  • terminal: not relevant (stdio transport)
Repro

Minimal Python script that speaks the framing ACP specifies (Content-Length: N\r\n\r\n + JSON):

import subprocess, json, threading

p = subprocess.Popen(
    ["opencode", "acp", "--cwd", "/tmp"],
    stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
)

def send(obj):
    data = json.dumps(obj).encode()
    p.stdin.write(f"Content-Length: {len(data)}\r\n\r\n".encode() + data)
    p.stdin.flush()

send({
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
        "protocolVersion": 1,
        "clientCapabilities": {
            "fs": {"read_text_file": True, "write_text_file": True},
            "terminal": True,
        },
    },
})

out = []
def reader():
    while True:
        b = p.stdout.read1(65536)
        if not b:
            break
        out.append(b)

t = threading.Thread(target=reader, daemon=True)
t.start()
t.join(20)

print("BYTES RECEIVED:", sum(len(b) for b in out))
print("STDOUT RAW:", repr(b"".join(out)[:600]))
p.kill()
print("STDERR:", p.stderr.read().decode()[:800])
Actual result
BYTES RECEIVED: 0
STDOUT RAW: b''
STDERR: Failed to parse JSON message: Content-Length: 184 2 | // @bun
3 | `);o=ue.pop()||"";for(let We of ue){let N=We.trim();if(N)try{let E=JSON.parse(N);i.enqueue(E)}catch(E){console.error("Failed to parse JSON message:",N,E)}}}...
                                                                                 ^
SyntaxError: JSON Parse error: Unexpected identifier "Content"
      at start (/$bunfs/root/chunk-rat302vz.js:3:78)
Expected result

The server should consume the Content-Length: N\r\n\r\n framing header, parse the JSON body, and respond with a valid initialize result (protocolVersion + capabilities), then accept session/new / session/prompt normally.

Analysis

The error comes from the stdio JSON parser (bundled bun code): it appears to split the incoming buffer on newlines and JSON.parse each chunk, so the framing header line Content-Length: 184\r\n\r\n leaks into the parser as if it were JSON — hence Unexpected identifier "Content". When the body happens to be delivered in a way that survives this naive splitting, parsing sometimes succeeds; that matches the symptom we saw in production (via the opencode acp command run by an ACP client): sessions sometimes start and then return empty end_turn turns with zero tool calls, instead of failing loudly.

Context

This was diagnosed while running coding lanes through opencode acp on opencode 1.18.29: every lane returned end_turn with 0 chars and 0 tool calls in ~3-7s. The exact same model (opencode run --model <provider>/<model> "Responda exatamente: STATUS OK") answers correctly via the regular CLI, which rules out model/provider issues — the bug is in the ACP server's stdio framing parser.

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.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.