Skip to content

OpenCode 2: /plannotator-annotate with extra words opens the file, and failures are shown - #1739

Merged
backnotprop merged 3 commits into
mainfrom
fix/oc2-annotate-prose-args
Oct 6, 2026
Merged

backnotprop merged 3 commits into
mainfrom
fix/oc2-annotate-prose-args

Conversation

@backnotprop

Copy link
Copy Markdown
Owner

Problem

On OpenCode 2 (seen in the 0.28.6 release smoke, also in 0.28.5), /plannotator-annotate . notes.md opened nothing and showed the user nothing. notes.md . and please notes.md failed the same way. The only trace was File not found: . notes.md in the OpenCode log. A single file and a real list of files worked, and OpenCode 1 opened notes.md correctly.

Cause

  1. When the words were not a list of files, buildAnnotateCliArgs in apps/opencode-plugin/cli-bridge.ts passed the whole string to the CLI as ONE argument: annotate ". notes.md" --json. The real CLI treats one argument as one path, so it never ran its tolerant resolution. That resolution, given separate words, drops . and opens notes.md. The fix(mod, opencode): name a review by what the CLI opened, not the typed words #1737 test passed only because its stub CLI split its one argument on whitespace and dropped ., which the real CLI does not do.
  2. A failed OpenCode 2 slash command was only logged (cliFailure / reportFailure). OpenCode 2 discards that log, so the user saw nothing at all.

Fix

  • Separate words. The new annotateCliTargets (cli-bridge) passes the slash command's words to the CLI as separate arguments when the input as a whole names nothing. The split is done by the shared annotateTargetWords (packages/shared/annotate-args.ts). It uses the same quote rule as review arguments (scanQuotedWords, now exported from review-args.ts): quotes group a path with spaces, and backslashes are kept so Windows paths work. An unterminated quote is read as prose (an apostrophe) and splits on whitespace. Known flags are dropped. The input stays ONE argument, as before, in three cases:

    • the whole input names an existing target (an unquoted path with spaces, my notes.md)
    • it is a single word
    • any word starts with -, so a mistyped flag, or a flag written in prose such as --require-approval, never reaches the CLI as a real flag.

    The plannotator tool's single target is never re-split, and strict flags are unchanged. The bundle pre-check reads the same words, so "a b.md" "c d.md" now works as a list of files.

  • Visible failure. A failed OpenCode 2 slash command (a refused argument or the CLI's startup error) now posts a transcript notice, Plannotator /plannotator-annotate failed: <reason> (createCommandFailureNotifier in v2-client.ts). It uses session.synthetic with a description and resume: false, the mechanism the session-URL notice already uses.

    • Why not session.prompt with queue (the tool's late-failure path)? It would start a model turn, and a slash command must never reach the model unless it says so. The tool uses it because there the agent was told to wait for a decision.
    • The model does not read it. The notice is for the person, so the context hook drops it from model requests, exactly like the URL notice. There is one shared, known gap: a notice that the host promotes alone at an idle boundary stays in that one request.
    • Other hosts. The OpenCode 1 CLI runtime now shows an error toast for the same failures (before, only some review errors had one). Tool launches carry neither notice and keep reporting failures as the tool result. A prompt-delivery failure after a decision is not shown as "failed".

Note: ./ notes.md still does not open notes.md, and that is by design. ./ is an explicit path to a folder, so the CLI reads two targets and gives its ambiguity error, the same as on OpenCode 1's embedded path. The user now sees that error.

Tests

  • apps/opencode-plugin/annotate-words-cli.test.ts (new) runs the REAL CLI (apps/hook/server/index.ts) through runNativeCommand, in a sandbox: temp HOME, XDG dirs and data dir.
    • . notes.md, notes.md . and please notes.md open notes.md, and the feedback names it as the target.
    • "my notes.md" please and unquoted my notes.md open the file.
    • A bad path is a synthetic notice with resume: false and no prompt.
    • ./ notes.md shows the ambiguity error.
    • All 6 fail against the old cli-bridge.ts.
  • plannotator-tool.test.ts: the "targets" stub now behaves like the real CLI. One argument is one path ("File not found"), and several arguments go through the shared selection. The fix(mod, opencode): name a review by what the CLI opened, not the typed words #1737 test now fails without the fix. The tool's startup-error test also checks that no failure notice is posted.
  • Unit tests: annotateTargetWords (quotes, apostrophes, backslashes, flags), annotateCliTargets, and dropping the failure notice from the model context.
  • bun test in apps/opencode-plugin (362 pass), apps/pi-extension and the relevant packages/shared tests pass, and bun run typecheck is clean. Not checked against a live OpenCode 2.

…ash-command failures

On OpenCode 2, `/plannotator-annotate . notes.md` (also `notes.md .`,
`please notes.md`) opened nothing and showed nothing. The CLI bridge passed
the words as ONE argument (`annotate ". notes.md" --json`), which the real
CLI reads as a single path ("File not found: . notes.md"), so its tolerant
resolution never ran. The failure only went to the log, which OpenCode 2
discards.

- annotateCliTargets: when the input names nothing as a whole, its words
  go to the CLI as separate arguments (shared annotateTargetWords: quotes
  group a path with spaces, an unterminated quote is prose, backslashes kept,
  known flags dropped). One argument as before for a whole-input match (an
  unquoted path with spaces), a single word, or any dash word. The tool's
  single target is never re-split.
- A failed OpenCode 2 slash command posts a transcript notice through
  session.synthetic with resume: false (no model turn), dropped from model
  requests like the session-URL notice. The OpenCode 1 CLI runtime shows an
  error toast. Tool launches keep reporting failures as the tool result.
- Tests: the stub CLI now reads one argument as one path like the real one;
  a new test runs the REAL CLI through runNativeCommand.
… requests

The context filter matched any user message starting with
'Plannotator /plannotator-annotate failed: ', so a person who pasted the
failure line they saw and asked about it lost that message from every
later model request (and the cached prefix shifted). The notice's text now
ends with a marker its visible description lacks, and the filter matches
only text ending with it.
…ate targets

annotateTargetWords grouped text between two prose apostrophes into one
word ('it's notes.md, don't' hid notes.md). A quote now groups only when it
opens and closes a word. Uses its own scanner, so review-args is back to
main unchanged.

A URL followed by prose went to the CLI as one 'URL' because the URL probe
matches on the scheme alone; a URL holding whitespace is now split like
other prose.
@backnotprop
backnotprop merged commit 1d9fe3f into main Oct 6, 2026
25 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.

1 participant