Scope extends beyond opencode — systematic build_exec_args() audit
While investigating the opencode dispatch bug, I audited all 28 integrations and found that the root cause is not specific to opencode — it's a systematic architectural weakness in how build_exec_args() is inherited.
Current state
Only 4 out of 15 CLI-capable integrations have verified, correct build_exec_args() implementations:
| Integration |
Base class |
CLI invocation |
Status |
| Claude |
SkillsIntegration |
claude -p "prompt" --model X --output-format json |
✅ Verified (-p is documented) |
| Gemini |
TomlIntegration |
gemini -p "prompt" -m X --output-format json |
✅ Verified (-p and -m are documented) |
| Codex |
SkillsIntegration |
codex exec "prompt" --model X --json |
✅ Custom override with exec subcommand |
| Copilot |
IntegrationBase |
copilot -p "prompt" --agent X --yolo --model X --output-format json |
✅ Custom override with dynamic --yolo |
The remaining 11 integrations with requires_cli: True all inherit build_exec_args() from their base class without verification that the CLI actually supports the generated flags:
| Integration |
Inherited invocation |
Risk |
| OpenCode |
opencode -p "prompt" --model X --output-format json |
❌ Confirmed broken — should be opencode run "prompt" -m X |
| Amp |
amp -p "prompt" --model X --output-format json |
⚠️ Unverified |
| Auggie |
auggie -p "prompt" --model X --output-format json |
⚠️ Unverified |
| CodeBuddy |
codebuddy -p "prompt" --model X --output-format json |
⚠️ Unverified |
| Forge |
forge -p "prompt" --model X --output-format json |
⚠️ Unverified |
| iFlow |
iflow -p "prompt" --model X --output-format json |
⚠️ Unverified |
| Junie |
junie -p "prompt" --model X --output-format json |
⚠️ Unverified |
| Kiro CLI |
kiro-cli -p "prompt" --model X --output-format json |
⚠️ Unverified |
| Pi |
pi -p "prompt" --model X --output-format json |
⚠️ Unverified |
| QoderCLI |
qodercli -p "prompt" --model X --output-format json |
⚠️ Unverified |
| Qwen |
qwen -p "prompt" --model X --output-format json |
⚠️ Unverified |
Additionally, 3 SkillsIntegration-based CLIs (Kimi, Shai, Vibe) and 1 TomlIntegration-based CLI (Tabnine) inherit the same -p pattern without verification.
There's also a data inconsistency: Goose has requires_cli: True but inherits from YamlIntegration, which doesn't override build_exec_args() — so it returns None, meaning CLI dispatch silently fails despite claiming CLI support.
Root cause
MarkdownIntegration.build_exec_args() hardcodes three assumptions that don't hold for all CLIs:
- Prompt mechanism:
-p flag (works for Claude/Gemini) vs. run/exec subcommand (opencode/codex) vs. other formats
- Model flag:
--model long form (most) vs. -m short form (Gemini, opencode) vs. not supported
- JSON output:
--output-format json (most) vs. --json (Codex) vs. -f json (opencode) vs. not supported
Every deviation requires a full method override, which is easy to forget (as the opencode bug shows).
Proposed fix: Declarative batch-mode attributes
Instead of requiring each integration to override build_exec_args(), introduce class-level attributes on IntegrationBase that make the common variations declarative:
class IntegrationBase(ABC):
exec_mode: str = "flag" # "flag" | "subcommand" | "none"
exec_prompt_flag: str = "-p" # flag used when exec_mode="flag"
exec_subcommand: str = "" # subcommand when exec_mode="subcommand"
exec_model_flag: str = "--model" # "--model" or "-m"
exec_json_args: tuple[str, ...] = ("--output-format", "json") # or ("--json",) or ()
Then build_exec_args() on IntegrationBase uses these attributes, and most integrations need zero method overrides:
# OpencodeIntegration — just attributes, no method override
class OpencodeIntegration(MarkdownIntegration):
key = "opencode"
exec_mode = "subcommand"
exec_subcommand = "run"
exec_model_flag = "-m"
exec_json_args = ()
...
# CodexIntegration — just attributes, no method override
class CodexIntegration(SkillsIntegration):
key = "codex"
exec_mode = "subcommand"
exec_subcommand = "exec"
exec_json_args = ("--json",)
...
Complex cases like Copilot (dynamic --yolo flag) still override build_exec_args() directly.
This makes CLI invocation patterns visible at a glance in the integration class, eliminates an entire class of bugs, and reduces boilerplate from ~15 lines per override to ~4 lines of attributes.
Originally posted as a comment on #2409
Scope extends beyond opencode — systematic
build_exec_args()auditWhile investigating the opencode dispatch bug, I audited all 28 integrations and found that the root cause is not specific to opencode — it's a systematic architectural weakness in how
build_exec_args()is inherited.Current state
Only 4 out of 15 CLI-capable integrations have verified, correct
build_exec_args()implementations:SkillsIntegrationclaude -p "prompt" --model X --output-format json-pis documented)TomlIntegrationgemini -p "prompt" -m X --output-format json-pand-mare documented)SkillsIntegrationcodex exec "prompt" --model X --jsonexecsubcommandIntegrationBasecopilot -p "prompt" --agent X --yolo --model X --output-format json--yoloThe remaining 11 integrations with
requires_cli: Trueall inheritbuild_exec_args()from their base class without verification that the CLI actually supports the generated flags:opencode -p "prompt" --model X --output-format jsonopencode run "prompt" -m Xamp -p "prompt" --model X --output-format jsonauggie -p "prompt" --model X --output-format jsoncodebuddy -p "prompt" --model X --output-format jsonforge -p "prompt" --model X --output-format jsoniflow -p "prompt" --model X --output-format jsonjunie -p "prompt" --model X --output-format jsonkiro-cli -p "prompt" --model X --output-format jsonpi -p "prompt" --model X --output-format jsonqodercli -p "prompt" --model X --output-format jsonqwen -p "prompt" --model X --output-format jsonAdditionally, 3 SkillsIntegration-based CLIs (Kimi, Shai, Vibe) and 1 TomlIntegration-based CLI (Tabnine) inherit the same
-ppattern without verification.There's also a data inconsistency: Goose has
requires_cli: Truebut inherits fromYamlIntegration, which doesn't overridebuild_exec_args()— so it returnsNone, meaning CLI dispatch silently fails despite claiming CLI support.Root cause
MarkdownIntegration.build_exec_args()hardcodes three assumptions that don't hold for all CLIs:-pflag (works for Claude/Gemini) vs.run/execsubcommand (opencode/codex) vs. other formats--modellong form (most) vs.-mshort form (Gemini, opencode) vs. not supported--output-format json(most) vs.--json(Codex) vs.-f json(opencode) vs. not supportedEvery deviation requires a full method override, which is easy to forget (as the opencode bug shows).
Proposed fix: Declarative batch-mode attributes
Instead of requiring each integration to override
build_exec_args(), introduce class-level attributes onIntegrationBasethat make the common variations declarative:Then
build_exec_args()onIntegrationBaseuses these attributes, and most integrations need zero method overrides:Complex cases like Copilot (dynamic
--yoloflag) still overridebuild_exec_args()directly.This makes CLI invocation patterns visible at a glance in the integration class, eliminates an entire class of bugs, and reduces boilerplate from ~15 lines per override to ~4 lines of attributes.
Originally posted as a comment on #2409