fix(opencode): strip backticks from plugin reminder to prevent silent command substitution (#1413)
The opencode plugin template embedded the reminder string with backticks
around `graphify query "<question>"`. Because the plugin prepends
`echo "<reminder>" && <cmd>` to the user's bash command, those backticks
triggered bash command substitution: every grep/rg/find invocation silently
ran `graphify query "<question>"` and substituted its output
("No matching nodes found.") into the reminder text shown to the agent.
This both corrupted tool output with graphify noise, and actually spawned
a graphify process + loaded graph.json + ran a BFS traversal with the
literal token <question> on every search.
Fix: remove the backticks. Adds a guard comment in the template so future
editors don't reintroduce the bug, and a regression test that asserts the
reminder string contains no backticks and no $() constructs.
This commit is contained in:
@@ -1380,6 +1380,12 @@ def _uninstall_kilo_plugin(project_dir: Path) -> None:
|
||||
_OPENCODE_PLUGIN_JS = """\
|
||||
// graphify OpenCode plugin
|
||||
// Injects a knowledge graph reminder before bash tool calls when the graph exists.
|
||||
//
|
||||
// IMPORTANT: keep the reminder string free of backticks and $(...) constructs.
|
||||
// The hook prepends `echo "<reminder>" && <cmd>` to the user's bash command;
|
||||
// backticks inside the double-quoted echo trigger bash command substitution,
|
||||
// which both corrupts tool output and silently executes the very graphify
|
||||
// command we are only suggesting. Plain words render fine in opencode's TUI.
|
||||
import { existsSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
@@ -1393,7 +1399,7 @@ export const GraphifyPlugin = async ({ directory }) => {
|
||||
|
||||
if (input.tool === "bash") {
|
||||
output.args.command =
|
||||
'echo "[graphify] knowledge graph at graphify-out/. For focused questions, run \\`graphify query \\"<question>\\"\\` (scoped subgraph, usually much smaller than GRAPH_REPORT.md) instead of grepping raw files. Read GRAPH_REPORT.md only for broad architecture context." && ' +
|
||||
'echo "[graphify] knowledge graph at graphify-out/. For focused questions, run graphify query with your question (scoped subgraph, usually much smaller than GRAPH_REPORT.md) instead of grepping raw files. Read GRAPH_REPORT.md only for broad architecture context." && ' +
|
||||
output.args.command;
|
||||
reminded = true;
|
||||
}
|
||||
|
||||
@@ -599,6 +599,28 @@ def test_opencode_agents_install_writes_plugin(tmp_path):
|
||||
assert "tool.execute.before" in plugin.read_text()
|
||||
|
||||
|
||||
def test_opencode_plugin_reminder_has_no_backticks(tmp_path):
|
||||
"""The bash reminder string must not contain backticks or $(...) (regression test for #1413).
|
||||
|
||||
The plugin prepends `echo "<reminder>" && <cmd>` to the user's bash command.
|
||||
Backticks or $() inside the reminder trigger bash command substitution
|
||||
when the echo runs, which both corrupts tool output and silently executes
|
||||
the very graphify command we are only suggesting.
|
||||
"""
|
||||
_agents_install(tmp_path, "opencode")
|
||||
plugin = tmp_path / ".opencode" / "plugins" / "graphify.js"
|
||||
body = plugin.read_text()
|
||||
# Extract the echoed reminder string literal between the double-quotes
|
||||
# of the `output.args.command = 'echo "..." && ' +` line.
|
||||
import re
|
||||
|
||||
m = re.search(r'echo "([^"]*)"', body)
|
||||
assert m, "echo reminder not found in plugin body"
|
||||
reminder = m.group(1)
|
||||
assert "`" not in reminder, f"backtick in reminder would trigger command substitution: {reminder!r}"
|
||||
assert "$(" not in reminder, f"$() in reminder would trigger command substitution: {reminder!r}"
|
||||
|
||||
|
||||
def test_opencode_agents_install_registers_plugin_in_config(tmp_path):
|
||||
"""opencode install registers the plugin in .opencode/opencode.json."""
|
||||
_agents_install(tmp_path, "opencode")
|
||||
|
||||
Reference in New Issue
Block a user