Skip to content

fix(cli): preserve --raw atoms verbatim in run handler (#9622)#9653

Merged
catrielmuller merged 4 commits into
Kilo-Org:mainfrom
truffle-dev:fix/cli-run-leading-dash-positional
Jun 2, 2026
Merged

fix(cli): preserve --raw atoms verbatim in run handler (#9622)#9653
catrielmuller merged 4 commits into
Kilo-Org:mainfrom
truffle-dev:fix/cli-run-leading-dash-positional

Conversation

@truffle-dev

Copy link
Copy Markdown
Contributor

Summary

Fixes #9622. When a user runs kilo run -- "- Who are you?", the run handler emits literal-quote bytes into the model prompt, producing "- Who are you?" instead of - Who are you?. The downstream pipeline rejects the leading-quote-then-dash sequence and the command fails ("❌ Can not run" per the OP).

The bug is in packages/opencode/src/cli/cmd/run.ts at the message assembler, which post-#4979 wraps every atom containing a space in synthesized quote bytes. That intent makes sense for atoms before -- (the user's shell quoting represents a word-binding boundary they want preserved), but it is wrong for atoms after --: yargs's populate-- semantics mean the user typed -- precisely to opt out of further parsing, so anything in args["--"] is already raw passthrough.

Investigation

Verified bug repro at HEAD (be0ea1990) and tag v7.1.23 (the version OP @hbrls reported on Windows 11 + PowerShell 7). Same handler at both points:

let message = [...args.message, ...(args["--"] || [])]
  .map((arg) => (arg.includes(" ") ? `"${arg.replace(/"/g, '\\"')}"` : arg))
  .join(" ")

The over-quote was introduced in be8116e2 ("fix: preserve argument boundaries in run command (#4979)"), which addressed a different case: shell-bound multi-word positionals like kilo run hello "world foo" bar losing their boundary on the model side. That intent is preserved by this PR; only the dashDash branch changes.

yargs itself parses kilo run -- "- Who are you?" correctly (verified via standalone yargs@18 trace in the issue thread): args.message = [], args["--"] = ["- Who are you?"]. The single-atom shape from yargs is what the assembler then re-wraps incorrectly.

Fix

Extract the message assembler to packages/opencode/src/cli/cmd/run-message.ts so it can be unit-tested without pulling in the full handler's dependency tree, and split the assembly per source:

export function buildRunMessage(positionals: string[], dashDash?: string[]): string {
  const quoted = positionals.map((arg) => (arg.includes(" ") ? `"${arg.replace(/"/g, '\\"')}"` : arg))
  return [...quoted, ...(dashDash ?? [])].join(" ")
}

Positionals (args.message) keep the wrap-quote behavior from #4979. dashDash (args["--"]) joins verbatim.

Tests

packages/opencode/test/cli/cmd/run.test.ts (new file) covers seven cases:

  • preserves shell-bound multi-word positionals via wrap-quote (Changeset version bump #4979 retained)
  • does not quote single-word positionals
  • escapes embedded double quotes inside positionals
  • passes args["--"] through verbatim without wrap-quote (The cli parse the starting dash in a prompt wrong #9622 root case)
  • does not synthesize quote bytes around dashDash atoms even when they contain spaces
  • combines positionals and dashDash with appropriate quoting per source
  • handles undefined and empty dashDash identically

Stash-bisect proof

Reverted run-message.ts to the pre-fix behavior (re-quote all atoms including dashDash) and ran the suite:

test/cli/cmd/run.test.ts:
  passes args['--'] through verbatim without wrap-quote (#9622)
    Expected: "- Who are you?"
    Received: ""- Who are you?""

  does not synthesize quote bytes around dashDash atoms even when they contain spaces
    Expected: "one two three"
    Received: ""one two" three"

  combines positionals and dashDash with appropriate quoting per source
    Expected: pre "fix arg" raw arg tail
    Received: pre "fix arg" "raw arg" tail

 4 pass / 3 fail

Restored the fix, full suite green:

 7 pass / 0 fail
Ran 7 tests across 1 file. [1.66s]

Validation

  • bun test test/cli/cmd/run.test.ts — 7 pass / 0 fail
  • bun x prettier --check on all three changed files — clean
  • bun x oxlint on all three changed files — 0 errors (8 pre-existing warnings in run.ts, none from this change)

Compatibility

No public API change. The handler still accepts the same args and emits the same shape of message to the session. Only the byte-level content of the message string changes for the args["--"] path, which is what the bug was.

Closes

Closes #9622

@@ -0,0 +1,11 @@
// Atoms before `--` are positional shell arguments where re-quoting around

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: New shared opencode file is missing the required Kilo annotation

This file lives under packages/opencode/ and does not have kilocode in its path, so the annotation checker treats it as shared upstream code. New shared files need the first non-empty line to be // kilocode_change - new file; without that, script/check-opencode-annotations.ts will flag every added line.

Comment thread packages/opencode/src/cli/cmd/run.ts Outdated
let message = [...args.message, ...(args["--"] || [])]
.map((arg) => (arg.includes(" ") ? `"${arg.replace(/"/g, '\\"')}"` : arg))
.join(" ")
let message = buildRunMessage(args.message, args["--"])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Shared opencode changes need kilocode_change coverage

This added line is in an upstream-owned file and is not inside a kilocode_change block or marked inline. The same applies to the new import, so the opencode annotation check will fail unless these additions are annotated or moved into a kilocode path.

@@ -0,0 +1,36 @@
import { describe, expect, test } from "bun:test"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: New shared opencode test file is missing the required Kilo annotation

Because this test is under packages/opencode/test/ but not a kilocode path, the annotation checker requires either a whole-file // kilocode_change - new file marker as the first non-empty line or placing the test under packages/opencode/test/kilocode/. Without that, CI will flag the added test lines.

@kilo-code-bot

kilo-code-bot Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The latest commit (859af50) resolves all previously flagged issues by moving files to Kilo-only directories:

  • run-message.ts: moved src/cli/cmd/src/kilocode/cli/cmd/ — no longer a shared opencode file, kilocode_change marker correctly removed ✓
  • run-message.test.ts: moved test/cli/cmd/test/kilocode/cli/cmd/ — kilo-only test path, marker correctly removed ✓
  • run.ts import: updated to @/kilocode/cli/cmd/run-message, // kilocode_change annotation retained on both the import and call site ✓
  • Parameter rename dashDashdash aligns with single-word naming style guide ✓

The fix logic is unchanged and correct: positionals retain wrap-quote behavior (preserving #4979), while args["--"] atoms are joined verbatim (fixing #9622). Test coverage covers all 7 cases.

Files Reviewed (3 files)
  • packages/opencode/src/kilocode/cli/cmd/run-message.ts
  • packages/opencode/src/cli/cmd/run.ts
  • packages/opencode/test/kilocode/cli/cmd/run-message.test.ts

Reviewed by claude-sonnet-4.6 · 561,008 tokens

Review guidance: REVIEW.md from base branch main

@truffle-dev

Copy link
Copy Markdown
Contributor Author

Friendly check-in. 16 days in with kilo-code-bot recommending merge and no human review yet. Happy to rebase if anything's drifted on main.

Atoms in `args["--"]` are raw passthrough per yargs `populate--`
semantics: the user typed `--` to opt out of further parsing, so
the assembler must not synthesize quote bytes around them. Re-quoting
raw atoms breaks leading-dash inputs like

  kilo run -- "- Who are you?"

by emitting `"- Who are you?"` (literal quotes) into the model prompt.

Atoms before `--` keep the existing wrap-quote behavior from Kilo-Org#4979 so
shell-bound multi-word positionals still preserve their boundaries.

Extracted the message assembler to `cli/cmd/run-message.ts` so it can
be unit-tested without pulling in the full handler's dependency tree.
Per AGENTS.md, every Kilo-specific change in shared opencode files must
carry kilocode_change markers so upstream merges can resolve mechanically.

Adds whole-file annotations to the new helper and test, single-line
annotations to the two modified lines in run.ts.
@truffle-dev truffle-dev force-pushed the fix/cli-run-leading-dash-positional branch from 00c0398 to 643810c Compare May 15, 2026 17:20
@truffle-dev

Copy link
Copy Markdown
Contributor Author

Rebased onto current main. Conflicts came from the upstream Flag import path change (@opencode-ai/core/flag/flag); kept the new path and re-applied the // kilocode_change annotations on the buildRunMessage import and call site. Lint/typecheck pass, bun test run.test.ts is 7/7 green.

@truffle-dev

Copy link
Copy Markdown
Contributor Author

Checking in. Last rebase landed on May 15, tests still green, no maintainer asks open on my side. Happy to rebase again if main has drifted; otherwise just flagging.

@catrielmuller catrielmuller merged commit 7a420b8 into Kilo-Org:main Jun 2, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The cli parse the starting dash in a prompt wrong

2 participants