Skip to content

Commit 8341c12

Browse files
committed
fix(integrations): wire SPECIFY_<KEY>_EXTRA_ARGS into Codex/Devin/Opencode/Copilot
Four integrations override `build_exec_args` and were silently ignoring the env-var hook introduced in the previous commit: - CodexIntegration (`codex exec ...`) - DevinIntegration (`devin -p ...`) - OpencodeIntegration (`opencode run ...`) - CopilotIntegration (`copilot -p ...`) Each now calls `self._apply_extra_args_env_var(args)` between the base argv and the canonical Spec Kit flags (matching the placement in `MarkdownIntegration`, `TomlIntegration`, and `SkillsIntegration`), so operator-injected flags cannot clobber `--model` / `--output-format` / `--json`. Adds 4 parameterized override-integration tests locking the wiring per agent. Also cleans up an inline `__import__("os").environ` in the fixture to a top-of-file `import os`. Drive-by typing fix: guard `self.registrar_config.get(...)` in `CopilotIntegration.add_commands` against the `None` case, matching the pattern already used in `base.py` for the same access. Addresses Copilot review on #2596.
1 parent 87ea00c commit 8341c12

5 files changed

Lines changed: 83 additions & 2 deletions

File tree

‎src/specify_cli/integrations/codex/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def build_exec_args(
3838
) -> list[str] | None:
3939
# Codex uses ``codex exec "prompt"`` for non-interactive mode.
4040
args: list[str] = ["codex", "exec", prompt]
41+
self._apply_extra_args_env_var(args)
4142
if model:
4243
args.extend(["--model", model])
4344
if output_json:

‎src/specify_cli/integrations/copilot/__init__.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ def build_exec_args(
139139
# (default: enabled). The deprecated SPECKIT_ALLOW_ALL_TOOLS
140140
# is also honoured as a fallback.
141141
args = ["copilot", "-p", prompt]
142+
self._apply_extra_args_env_var(args)
142143
if _allow_all():
143144
args.append("--yolo")
144145
if model:
@@ -358,7 +359,11 @@ def _setup_default(
358359
created: list[Path] = []
359360

360361
script_type = opts.get("script_type", "sh")
361-
arg_placeholder = self.registrar_config.get("args", "$ARGUMENTS")
362+
arg_placeholder = (
363+
self.registrar_config.get("args", "$ARGUMENTS")
364+
if self.registrar_config
365+
else "$ARGUMENTS"
366+
)
362367

363368
# 1. Process and write command files as .agent.md
364369
for src_file in templates:

‎src/specify_cli/integrations/devin/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def build_exec_args(
4949
kept on the integration for tool detection.
5050
"""
5151
args = [self.key, "-p", prompt]
52+
self._apply_extra_args_env_var(args)
5253
if model:
5354
args.extend(["--model", model])
5455
return args

‎src/specify_cli/integrations/opencode/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def build_exec_args(
3737
args.extend(["--command", command])
3838
message = remainder
3939

40+
self._apply_extra_args_env_var(args)
4041
if model:
4142
args.extend(["-m", model])
4243
if output_json:

‎tests/integrations/test_extra_args.py‎

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Tests for the per-integration `SPECIFY_<KEY>_EXTRA_ARGS` env-var hook
22
in `SkillsIntegration.build_exec_args`. See issue #2595."""
33

4+
import os
5+
46
import pytest
57

68
from specify_cli.integrations.base import SkillsIntegration
@@ -72,7 +74,7 @@ class _NoCliStub(SkillsIntegration):
7274
def _clean_extra_args_env(monkeypatch):
7375
"""Strip any leaked SPECIFY_*_EXTRA_ARGS from the test env so a
7476
developer's shell setting doesn't pollute results."""
75-
for key in list(__import__("os").environ):
77+
for key in list(os.environ):
7678
if key.startswith("SPECIFY_") and key.endswith("_EXTRA_ARGS"):
7779
monkeypatch.delenv(key, raising=False)
7880

@@ -161,3 +163,74 @@ def test_requires_cli_false_returns_none(monkeypatch):
161163
hook is never reached and no argv is built."""
162164
monkeypatch.setenv("SPECIFY_NO_CLI_EXTRA_ARGS", "--should-not-appear")
163165
assert _NoCliStub().build_exec_args("p") is None
166+
167+
168+
# ---------------------------------------------------------------------------
169+
# Override-integration coverage
170+
#
171+
# CodexIntegration, DevinIntegration, OpencodeIntegration and
172+
# CopilotIntegration each override `build_exec_args` rather than using the
173+
# base implementations. The env-var hook must be wired into every override
174+
# so the documented behaviour ("works for every requires_cli integration")
175+
# is honoured. These tests lock that contract per integration.
176+
# ---------------------------------------------------------------------------
177+
178+
179+
def test_codex_integration_honours_extra_args(monkeypatch):
180+
from specify_cli.integrations.codex import CodexIntegration
181+
182+
monkeypatch.setenv("SPECIFY_CODEX_EXTRA_ARGS", "--sandbox read-only")
183+
args = CodexIntegration().build_exec_args("p", model="gpt-5")
184+
assert args == [
185+
"codex",
186+
"exec",
187+
"p",
188+
"--sandbox",
189+
"read-only",
190+
"--model",
191+
"gpt-5",
192+
"--json",
193+
]
194+
195+
196+
def test_devin_integration_honours_extra_args(monkeypatch):
197+
from specify_cli.integrations.devin import DevinIntegration
198+
199+
monkeypatch.setenv("SPECIFY_DEVIN_EXTRA_ARGS", "--no-confirm")
200+
args = DevinIntegration().build_exec_args("p")
201+
assert args == ["devin", "-p", "p", "--no-confirm"]
202+
203+
204+
def test_opencode_integration_honours_extra_args(monkeypatch):
205+
from specify_cli.integrations.opencode import OpencodeIntegration
206+
207+
monkeypatch.setenv("SPECIFY_OPENCODE_EXTRA_ARGS", "--quiet")
208+
args = OpencodeIntegration().build_exec_args("p")
209+
assert args == [
210+
"opencode",
211+
"run",
212+
"--quiet",
213+
"--format",
214+
"json",
215+
"p",
216+
]
217+
218+
219+
def test_copilot_integration_honours_extra_args(monkeypatch):
220+
from specify_cli.integrations.copilot import CopilotIntegration
221+
222+
# Disable --yolo so the argv shape stays deterministic.
223+
monkeypatch.setenv("SPECKIT_COPILOT_ALLOW_ALL_TOOLS", "0")
224+
monkeypatch.setenv(
225+
"SPECIFY_COPILOT_EXTRA_ARGS", "--allow-tool 'shell(echo)'"
226+
)
227+
args = CopilotIntegration().build_exec_args("p")
228+
assert args == [
229+
"copilot",
230+
"-p",
231+
"p",
232+
"--allow-tool",
233+
"shell(echo)",
234+
"--output-format",
235+
"json",
236+
]

0 commit comments

Comments
 (0)