Repository navigation
Description
Summary
opencode run adds literal double quotes around a positional prompt passed as a single argument containing spaces. The stored user message differs from the same text supplied as separate arguments or stdin. Scripts that need exact prompt text receive altered input.
Steps to reproduce
Use the published release listed under Runtime or environment. Install it in a disposable prefix and use a fresh HOME and XDG state. The following commands were executed from an empty git project with no project configuration. All model commands use a private standalone server. The model catalog already listed the selected free model before the runs.
W=$(mktemp -d /tmp/agent-work.XXXXXX)
npm install --prefix "$W/oc" @opencode/[email protected]
mkdir -p "$W/h" "$W/project"
git init -q -b main "$W/project"
cd "$W/project"
B="$W/oc/node_modules/.bin/opencode"
oc() {
env -i PATH="$PATH" HOME="$W/h" \
XDG_CONFIG_HOME="$W/h/.config" \
XDG_DATA_HOME="$W/h/.local/share" \
XDG_CACHE_HOME="$W/h/.cache" \
XDG_STATE_HOME="$W/h/.local/state" \
OPENCODE_DISABLE_PROJECT_CONFIG=1 "$B" "$@"
}
oc models --standalone
oc run --standalone --format json --model opencode/glm-5.3-flash-free \
--title 'Prompt preservation check' 'Reply with: one two' \
</dev/null > "$W/single.jsonl"
oc run --standalone --format json --model opencode/glm-5.3-flash-free \
--title 'Prompt preservation check' Reply with: one two \
</dev/null > "$W/separate.jsonl"
printf '%s' 'Reply with: one two' | \
oc run --standalone --format json --model opencode/glm-5.3-flash-free \
--title 'Prompt preservation check' > "$W/stdin.jsonl"
for case in single separate stdin; do
S=$(jq -r 'select(.sessionID) | .sessionID' "$W/$case.jsonl" | head -n 1)
oc session export --standalone "$S" | jq -c \
'{user:.messages[0].text,assistant:[.messages[]|select(.type=="assistant")|{model:.model.id,cost,finish}]}'
doneThe three CLI invocations and subsequent exports above reflect the executed argument forms. The investigator used a subprocess runner with an external deadline and wrote outputs into per-case folders; that wrapper does not modify argv or stdin. If npm disables lifecycle scripts, run the installed package's postinstall.mjs before using its executable. The isolated reproduction required that installation step because the local package manager disabled scripts.
Expected behavior
The positional argument is documented as the message to send. Shell grouping quotes determine argv boundaries and should not become literal message content. All three forms above should store the same text, Reply with: one two. Quotes intentionally present within the supplied text should remain distinguishable from shell syntax.
Actual behavior
All three runs exited with code 0. Each export showed an assistant completion using glm-5.3-flash-free, recorded cost 0, and finish stop. The single-argument form stored extra quote characters. The separate-argument and stdin controls preserved the input text.
{"case":"single","user":"\"Reply with: one two\"","assistant":[{"model":"glm-5.3-flash-free","cost":0,"finish":"stop"}]}
{"case":"separate","user":"Reply with: one two","assistant":[{"model":"glm-5.3-flash-free","cost":0,"finish":"stop"}]}
{"case":"stdin","user":"Reply with: one two","assistant":[{"model":"glm-5.3-flash-free","cost":0,"finish":"stop"}]}Evidence
- Expected source: The measured
opencode run --helpdescribes the positionalmessage...argument asMessage to send; the release command declaration agrees. The two successful controls establish the same expected text for these inputs. - Failure source: Fresh standalone CLI runs and session exports produced the JSON quoted under Actual behavior.
- Evidence provenance: observed
- Local verification: reproduced
- Reproduction completeness: complete
The local run outputs are the primary violation evidence. The published binary was executed. The current default branch was inspected statically, not built or executed.
The release input handling calls formatMessage on the parsed positional arguments before merging stdin. The release helper adds literal wrapping quotes to each argument containing a space, then joins the arguments. When there is no positional message, mergeInput returns the piped text unchanged. The default-branch helper still contains the same implementation. The release prompt submission passes that message as text to the prompt API.
Restoration check
Repeat the three argument forms and inspect exported user text. Each should contain exactly Reply with: one two, without new wrapping quotes. Keep the separate-argument and stdin controls passing. Also verify that intentional embedded quotes and multiline text retain their literal content, so removing the extra wrapper does not introduce a different alteration. Those additional inputs were not exercised in this local rerun.
Runtime or environment
- Published executable:
opencode v2.0.31, installed from npm package@opencode/cli2.0.31, the current npmlatestdist-tag when checked. - Corresponding release source: tag
v2.0.31, commit22728d67461260d56bb9796b9b8f36e1de47202e. - Current default source:
dev, commited772f5009755b5262805a7ba07e66e1417f140a; static inspection only. - Platform: Darwin 27.0.0, arm64.
- Provider and model: OpenCode Zen,
opencode/glm-5.3-flash-free, fresh state without provider credentials. - Server route:
--standalonefor model listing, runs, and session exports.
Additional context
Issue #43923 originally reported this behavior on v1. A later comment confirmed the stored-message alteration and stdin workaround on v2.0.10. The issue was then closed in the automated v1 issue sweep. Its closing comment asks users to reopen or open a new issue with their 2.0 version if the behavior is still relevant. This is that 2.0 report: the published release listed above reproduces it, and the inspected dev source retains the same helper.
PR #50245 is a closed, unmerged fix proposal for the same helper and spaced-argument trigger. Its withdrawal explanation refers to the existing issue. It does not establish a shipped fix.
Triage assessment
- Impact level: P2
- Assessment status: supported
- Impact basis: A supported automation entry point changes literal prompt text for a spaced positional argument. The reproduction confirms stored-message alteration, while downstream model-answer consequences and population frequency were not measured.
- Workaround status: available
- Workaround basis: Sending the identical prompt through stdin with
printf '%s'preserved the stored text in the successful control run without adding a newline or requiring broader permissions.
Activity
opencode-agent commentedon Oct 16, 2026
Thanks for the detailed report. I reproduced this on 2.0.31, and it still happens on the latest
dev(ed772f5).I used a fresh HOME/XDG directory, an empty git project and a local mock OpenAI-compatible provider, so no credentials were needed. I ran the three forms from your report and checked each stored user message with
opencode session export:opencode run 'Reply with: one two'stores"Reply with: one two", with literal quotesopencode run Reply with: one twostoresReply with: one twoprintf '%s' 'Reply with: one two' | opencode runstoresReply with: one twoThe cause matches what you found:
formatMessageinpackages/cli/src/run/run.tswraps each argument that contains a space in quotes before joining them. Until this changes, piping the prompt through stdin keeps the text exactly as given.The script below reproduces it. Related: #43923 (the original v1 report) and #50245 (an earlier fix proposal that was closed without being merged).
Reproduction script (repro.sh)
Run it:
bash repro.sh