Summary
When /loop is invoked via opencode run (both headless and -i interactive mode), the plugin's deterministic command parser (handleUserCommand, wired to the command.execute.before hook) never runs. The raw $ARGUMENTS go straight to the model, which improvises via the loop_schedule tool. As a result, every documented input guard in the README is silently bypassed in this mode.
Environment
- opencode 1.18.3 (also reproduces the wiring described for
opencode run instances in the README)
- opencode-plugin-loop 0.7.2, installed globally per README (
plugin: ["opencode-plugin-loop"] in ~/.config/opencode/opencode.json, command defined with template: "$ARGUMENTS")
- macOS (darwin), Node v24
- Model: deepseek-v4-flash via OpenAI-compatible provider (behavior is model-dependent by construction — that's the point of this issue)
Minimal reproduction
mkdir /tmp/loop-test && cd /tmp/loop-test
# Case 1: bare interval — README says this must return
# "❌ Missing prompt after interval \"5m\". Usage: /loop <interval> <prompt> — see /loop help."
opencode run "/loop 5m"
# Case 2: cron syntax — README says this must return
# "❌ Cron expressions are not supported. ..."
opencode run "/loop */5 * * * * check something"
Expected (per README, "Upgrading to 0.4.0" section)
scheduling-like input that used to silently create an Adaptive task (cron syntax, bare intervals like /loop 5m, unknown flags) now returns an explicit error pointing at /loop help.
This is implemented in src/scheduler.ts (handleUserCommand, e.g. the looksLikeCron rejection at line ~300), reachable only through the command.execute.before hook in src/index.ts.
Actual
Case 1 — the model creates a real fixed task with a self-invented placeholder prompt:
⚙ loop_schedule {"action":"create","intervalMs":300000,"mode":"fixed",
"prompt":"The user requested a loop every 5 minutes. Ask them what they'd like to do."}
Created `/loop` task `0jg2mox8` — fires every 5 minutes.
Case 2 — the cron expression is silently converted to a fixed interval instead of rejected:
⚙ loop_schedule {"action":"create","prompt":"check something","intervalMs":300000,"mode":"fixed"}
Created loop task `1rvh3oge` — runs every 5 minutes (fixed mode) ...
Task persisted in .opencode/cache/loop/tasks.json (mode fixed, intervalMs 300000). Reproduced identically with opencode run -i under a pseudo-TTY, so this is not specific to headless mode.
Additionally, /loop help in opencode run prints model-generated help (derived from the tool schema) rather than the canonical LOOP_HELP text from src/scheduler.ts.
Impact
In opencode run, all deterministic behavior documented in the README — missing-prompt errors, cron rejection, unknown-flag rejection, exact help text — is replaced by model improvisation. Junk tasks with meta-prompts ("Ask them what they'd like to do") actually get scheduled and would fire every 5 minutes in a long-lived process.
Suspected cause
command.execute.before is not emitted by opencode when a command is executed through opencode run (with or without -i), so src/index.ts's interception of input.command === "loop" never happens and the command template ($ARGUMENTS) is sent to the model unguarded. I could not verify the full TUI path headlessly; if the hook fires correctly there, the plugin may need a run-mode fallback (e.g. input validation inside loop_schedule itself, mirroring handleUserCommand's guards).
Notes
- Existing test suite (164 tests) passes; ephemeral task cleanup, instance lock takeover, and per-session scoping all verified working in real
opencode run usage.
Summary
When
/loopis invoked viaopencode run(both headless and-iinteractive mode), the plugin's deterministic command parser (handleUserCommand, wired to thecommand.execute.beforehook) never runs. The raw$ARGUMENTSgo straight to the model, which improvises via theloop_scheduletool. As a result, every documented input guard in the README is silently bypassed in this mode.Environment
opencode runinstances in the README)plugin: ["opencode-plugin-loop"]in~/.config/opencode/opencode.json, command defined withtemplate: "$ARGUMENTS")Minimal reproduction
Expected (per README, "Upgrading to 0.4.0" section)
This is implemented in
src/scheduler.ts(handleUserCommand, e.g. thelooksLikeCronrejection at line ~300), reachable only through thecommand.execute.beforehook insrc/index.ts.Actual
Case 1 — the model creates a real fixed task with a self-invented placeholder prompt:
Case 2 — the cron expression is silently converted to a fixed interval instead of rejected:
Task persisted in
.opencode/cache/loop/tasks.json(mode fixed, intervalMs 300000). Reproduced identically withopencode run -iunder a pseudo-TTY, so this is not specific to headless mode.Additionally,
/loop helpinopencode runprints model-generated help (derived from the tool schema) rather than the canonicalLOOP_HELPtext fromsrc/scheduler.ts.Impact
In
opencode run, all deterministic behavior documented in the README — missing-prompt errors, cron rejection, unknown-flag rejection, exact help text — is replaced by model improvisation. Junk tasks with meta-prompts ("Ask them what they'd like to do") actually get scheduled and would fire every 5 minutes in a long-lived process.Suspected cause
command.execute.beforeis not emitted by opencode when a command is executed throughopencode run(with or without-i), sosrc/index.ts's interception ofinput.command === "loop"never happens and the command template ($ARGUMENTS) is sent to the model unguarded. I could not verify the full TUI path headlessly; if the hook fires correctly there, the plugin may need a run-mode fallback (e.g. input validation insideloop_scheduleitself, mirroringhandleUserCommand's guards).Notes
opencode runusage.