From bba473c223dc298fb76ecf8f87f86d5d2b8ed11c Mon Sep 17 00:00:00 2001 From: Ali jawwad <33836051+jawwad-ali@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:49:53 +0500 Subject: [PATCH] fix(integrations): cursor-agent honors executable/extra-args env overrides (#3265) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(integrations): cursor-agent ignores executable/extra-args env overrides cursor-agent's build_exec_args() hardcoded self.key as argv[0] and never called _apply_extra_args_env_var(), so the documented SPECKIT_INTEGRATION_CURSOR_AGENT_EXECUTABLE (issue #2596) and SPECKIT_INTEGRATION_CURSOR_AGENT_EXTRA_ARGS (issue #2595) hooks were silently dropped — unlike every other CLI-dispatch integration (codex, devin). Route argv[0] through _resolve_executable() and apply the extra-args hook after the mandatory headless flags, mirroring the twins. Co-Authored-By: Claude Opus 4.8 * test(integrations): pin extra-args insertion order for cursor-agent Per Copilot feedback: the extra-args override test only asserted the injected tokens were present, not that they land before Spec Kit's canonical --model / --output-format flags. Exercise build_exec_args with both a model and JSON output and assert the extra args are inserted before --model / --output-format (and the canonical flags stay intact and paired). Verified this fails if the _apply_extra_args_env_var call is moved after the flag extends. Co-Authored-By: Claude Opus 4.8 --------- Co-authored-by: Claude Opus 4.8 --- .../integrations/cursor_agent/__init__.py | 10 +++- .../test_integration_cursor_agent.py | 49 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/specify_cli/integrations/cursor_agent/__init__.py b/src/specify_cli/integrations/cursor_agent/__init__.py index 2c328b2fd..07f2a6318 100644 --- a/src/specify_cli/integrations/cursor_agent/__init__.py +++ b/src/specify_cli/integrations/cursor_agent/__init__.py @@ -75,7 +75,15 @@ class CursorAgentIntegration(SkillsIntegration): either drops tool calls or exits non-zero on the first approval prompt. """ - args = [self.key, "-p", "--trust", "--approve-mcps", "--force", prompt] + args = [ + self._resolve_executable(), + "-p", + "--trust", + "--approve-mcps", + "--force", + prompt, + ] + self._apply_extra_args_env_var(args) if model: args.extend(["--model", model]) if output_json: diff --git a/tests/integrations/test_integration_cursor_agent.py b/tests/integrations/test_integration_cursor_agent.py index 32318dc90..7b6822f7e 100644 --- a/tests/integrations/test_integration_cursor_agent.py +++ b/tests/integrations/test_integration_cursor_agent.py @@ -125,6 +125,55 @@ class TestCursorAgentCliDispatch: assert argv is not None assert argv[0] == "cursor-agent" + def test_build_exec_args_honors_executable_override(self, monkeypatch): + """``SPECKIT_INTEGRATION_CURSOR_AGENT_EXECUTABLE`` overrides argv[0]. + + Every other CLI-dispatch integration (codex, devin, ...) routes + argv[0] through ``_resolve_executable()`` so operators can pin a + binary path (issue #2596). cursor-agent hardcoded ``self.key`` and + silently ignored the documented override. + """ + monkeypatch.setenv( + "SPECKIT_INTEGRATION_CURSOR_AGENT_EXECUTABLE", "/custom/cursor" + ) + i = get_integration("cursor-agent") + args = i.build_exec_args("/speckit-plan", output_json=False) + assert args[0] == "/custom/cursor" + # The mandatory headless flags must still be present. + for flag in ("-p", "--trust", "--approve-mcps", "--force"): + assert flag in args + + def test_build_exec_args_honors_extra_args_override(self, monkeypatch): + """``SPECKIT_INTEGRATION_CURSOR_AGENT_EXTRA_ARGS`` flags are injected + *before* Spec Kit's canonical ``--model`` / ``--output-format`` flags. + + The ``_apply_extra_args_env_var()`` hook (issue #2595) was never + invoked by cursor-agent, so operator-supplied flags were dropped. + Insertion order is the real contract: extra args must land after the + mandatory headless flags but before ``--model`` / ``--output-format``, + so they cannot clobber, displace, or reorder Spec Kit's canonical + trailing flags. Exercise with both a model and JSON output so both + canonical flags are present to pin against. + """ + monkeypatch.setenv( + "SPECKIT_INTEGRATION_CURSOR_AGENT_EXTRA_ARGS", "--foo bar" + ) + i = get_integration("cursor-agent") + args = i.build_exec_args( + "/speckit-plan", model="sonnet-4-thinking", output_json=True + ) + assert "--foo" in args + assert "bar" in args + # "bar" is the value of "--foo": the tokens stay adjacent and in order. + assert args.index("bar") == args.index("--foo") + 1 + # Extra args are inserted before the canonical flags, so they cannot + # clobber or reorder them (the behavioral contract this test guards). + assert args.index("--foo") < args.index("--model") + assert args.index("--foo") < args.index("--output-format") + # The canonical flags themselves remain intact and correctly paired. + assert args[args.index("--model") + 1] == "sonnet-4-thinking" + assert args[args.index("--output-format") + 1] == "json" + def test_build_command_invocation_uses_hyphenated_skill_name(self): """SkillsIntegration: /speckit-plan (not /speckit.plan).""" i = get_integration("cursor-agent")