From c4ec01cf46a6ff66763fd343ca67d0efe0e7ace4 Mon Sep 17 00:00:00 2001 From: kartsan03 Date: Tue, 29 Sep 2026 17:42:34 +0000 Subject: [PATCH 1/4] fix(integrations): stop dispatching workflow steps to SHAI The SHAI CLI has no -p or --model flag and takes every argument as text for its auto-fix agent, which exits 0, so a workflow step targeting SHAI reported success without running the command. SHAI only reads headless prompts from stdin and never loads .shai/commands, so opt it out of CLI dispatch. The step error for an integration without dispatch now names that cause as well as a missing CLI. Refs #2416 Assisted-by: Claude Code (model: Claude Opus 5.5, autonomous) --- docs/reference/integrations.md | 2 +- src/specify_cli/integrations/shai/__init__.py | 27 +++++++++++ .../workflows/step/command/__init__.py | 3 +- .../workflows/step/prompt/__init__.py | 3 +- tests/integrations/test_integration_shai.py | 46 +++++++++++++++++++ 5 files changed, 78 insertions(+), 3 deletions(-) diff --git a/docs/reference/integrations.md b/docs/reference/integrations.md index 7ae12d9468..007bd5fab5 100644 --- a/docs/reference/integrations.md +++ b/docs/reference/integrations.md @@ -42,7 +42,7 @@ The Specify CLI supports a wide range of AI coding agents. When you run `specify | [Qoder CLI](https://qoder.com/cli) | `qodercli` | | | [Qwen Code](https://github.com/QwenLM/qwen-code) | `qwen` | | | [RovoDev](https://www.atlassian.com/software/rovo-dev) | `rovodev` | Generates `.rovodev/skills/`, prompt wrappers, and `prompts.yml`; runtime dispatch uses `acli rovodev` | -| [SHAI (OVHcloud)](https://github.com/ovh/shai) | `shai` | | +| [SHAI (OVHcloud)](https://github.com/ovh/shai) | `shai` | Workflow `command` and `prompt` steps can't run through SHAI: its CLI has no argument form that runs a prompt, so those steps fail | | [Tabnine CLI](https://docs.tabnine.com/main/getting-started/tabnine-cli) | `tabnine` | | | [Trae](https://www.trae.ai/) | `trae` | Skills-based integration; skills are installed automatically | | [ZCode](https://zcode.z.ai/) | `zcode` | Skills-based integration; installs skills into `.zcode/skills/` and invokes them as `$speckit-` | diff --git a/src/specify_cli/integrations/shai/__init__.py b/src/specify_cli/integrations/shai/__init__.py index 8be9596bf1..c932a6f26d 100644 --- a/src/specify_cli/integrations/shai/__init__.py +++ b/src/specify_cli/integrations/shai/__init__.py @@ -1,5 +1,11 @@ """SHAI CLI integration.""" +from __future__ import annotations + +from collections.abc import Mapping, Sequence +from pathlib import Path +from typing import Any + from ..base import MarkdownIntegration @@ -19,3 +25,24 @@ class ShaiIntegration(MarkdownIntegration): "extension": ".md", } multi_install_safe = True + + def build_exec_args( + self, + prompt: str, + *, + model: str | None = None, + output_json: bool = True, + integration_args: Sequence[str] | None = None, + integration_options: Mapping[str, Any] | None = None, + project_root: Path | None = None, + ) -> list[str] | None: + # SHAI has no argv form that runs a prompt. Its CLI collects every + # argument, `-p` and `--model` included, as text for its auto-fix + # agent (`trailing_var_arg`/`allow_hyphen_values` in + # shai-cli/src/main.rs of ovh/shai), which then exits 0, so the + # inherited `shai -p ` let workflow steps report success + # without running the command (#2416). Headless prompts are read from + # stdin only, and `.shai/commands` is never loaded, so opt out of + # CLI dispatch and let those steps fail instead. + self.validate_runtime_config(integration_args, integration_options) + return None diff --git a/src/specify_cli/workflows/step/command/__init__.py b/src/specify_cli/workflows/step/command/__init__.py index 439007112f..ebe1f36292 100644 --- a/src/specify_cli/workflows/step/command/__init__.py +++ b/src/specify_cli/workflows/step/command/__init__.py @@ -189,7 +189,8 @@ def execute(self, config: dict[str, Any], context: StepContext) -> StepResult: output=output, error=( f"Cannot dispatch command {command!r}: " - f"integration {integration!r} CLI not found or not installed. " + f"integration {integration!r} CLI not found or not installed, " + f"or the integration does not support CLI dispatch. " f"Install the CLI tool or check 'specify integration list'." ), ) diff --git a/src/specify_cli/workflows/step/prompt/__init__.py b/src/specify_cli/workflows/step/prompt/__init__.py index 9c2e683bf4..c0568c9258 100644 --- a/src/specify_cli/workflows/step/prompt/__init__.py +++ b/src/specify_cli/workflows/step/prompt/__init__.py @@ -137,7 +137,8 @@ def execute(self, config: dict[str, Any], context: StepContext) -> StepResult: error=( f"Cannot dispatch prompt: " f"integration {integration!r} " - f"CLI not found or not installed." + f"CLI not found or not installed, " + f"or the integration does not support CLI dispatch." ), ) diff --git a/tests/integrations/test_integration_shai.py b/tests/integrations/test_integration_shai.py index fc2b60c3f2..8faf166415 100644 --- a/tests/integrations/test_integration_shai.py +++ b/tests/integrations/test_integration_shai.py @@ -1,5 +1,12 @@ """Tests for ShaiIntegration.""" +from unittest.mock import MagicMock, patch + +from specify_cli.integrations import get_integration +from specify_cli.workflows.base import StepContext, StepStatus +from specify_cli.workflows.step.command import CommandStep +from specify_cli.workflows.step.prompt import PromptStep + from .test_integration_base_markdown import MarkdownIntegrationTests @@ -8,3 +15,42 @@ class TestShaiIntegration(MarkdownIntegrationTests): FOLDER = ".shai/" COMMANDS_SUBDIR = "commands" REGISTRAR_DIR = ".shai/commands" + + +class TestShaiCliDispatch: + """SHAI has no argv form that runs a prompt (#2416). + + Its CLI treats every argument, `-p` included, as text for its auto-fix + agent and exits 0, so dispatching `shai -p ` marked workflow steps + completed without running the command. + """ + + def test_build_exec_args_opts_out(self): + integration = get_integration("shai") + assert integration.build_exec_args("/speckit.plan", model="m") is None + + def _run(self, step, config, tmp_path): + ctx = StepContext(default_integration="shai", project_root=str(tmp_path)) + exited_ok = MagicMock(returncode=0, stdout="", stderr="") + with patch("shutil.which", return_value="/usr/local/bin/shai"), \ + patch("subprocess.run", return_value=exited_ok) as run: + result = step.execute(config, ctx) + return result, run + + def test_command_step_fails_instead_of_running_shai(self, tmp_path): + result, run = self._run( + CommandStep(), {"id": "plan", "command": "speckit.plan"}, tmp_path + ) + assert result.status == StepStatus.FAILED + assert result.output["dispatched"] is False + assert "does not support CLI dispatch" in result.error + run.assert_not_called() + + def test_prompt_step_fails_instead_of_running_shai(self, tmp_path): + result, run = self._run( + PromptStep(), {"id": "ask", "prompt": "Summarize the spec"}, tmp_path + ) + assert result.status == StepStatus.FAILED + assert result.output["dispatched"] is False + assert "does not support CLI dispatch" in result.error + run.assert_not_called() From b5512b0d49d9c7660ead6c2005ec7e16ec32e2c4 Mon Sep 17 00:00:00 2001 From: kartsan03 Date: Wed, 30 Sep 2026 14:14:48 +0000 Subject: [PATCH 2/4] docs(integrations): say why SHAI argv routes can't run Spec Kit commands SHAI does take headless prompt text as positional arguments, on stdin and through `shai agent `, so "no argv form that runs a prompt" was wrong. Every route goes to its auto-fix agent and .shai/commands is never loaded, which is why dispatch stays disabled. Reword the code comment, the test docstring and the integrations reference to say that. Refs #2416 Assisted-by: Claude Code (model: claude-opus-5-5, autonomous) --- docs/reference/integrations.md | 2 +- src/specify_cli/integrations/shai/__init__.py | 18 ++++++++++-------- tests/integrations/test_integration_shai.py | 9 +++++---- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/reference/integrations.md b/docs/reference/integrations.md index 007bd5fab5..c6c94180ee 100644 --- a/docs/reference/integrations.md +++ b/docs/reference/integrations.md @@ -42,7 +42,7 @@ The Specify CLI supports a wide range of AI coding agents. When you run `specify | [Qoder CLI](https://qoder.com/cli) | `qodercli` | | | [Qwen Code](https://github.com/QwenLM/qwen-code) | `qwen` | | | [RovoDev](https://www.atlassian.com/software/rovo-dev) | `rovodev` | Generates `.rovodev/skills/`, prompt wrappers, and `prompts.yml`; runtime dispatch uses `acli rovodev` | -| [SHAI (OVHcloud)](https://github.com/ovh/shai) | `shai` | Workflow `command` and `prompt` steps can't run through SHAI: its CLI has no argument form that runs a prompt, so those steps fail | +| [SHAI (OVHcloud)](https://github.com/ovh/shai) | `shai` | Workflow `command` and `prompt` steps can't run through SHAI: its CLI accepts prompt text (as arguments, on stdin, or through `shai agent`), but hands it to its auto-fix agent and never loads `.shai/commands`, so those steps fail | | [Tabnine CLI](https://docs.tabnine.com/main/getting-started/tabnine-cli) | `tabnine` | | | [Trae](https://www.trae.ai/) | `trae` | Skills-based integration; skills are installed automatically | | [ZCode](https://zcode.z.ai/) | `zcode` | Skills-based integration; installs skills into `.zcode/skills/` and invokes them as `$speckit-` | diff --git a/src/specify_cli/integrations/shai/__init__.py b/src/specify_cli/integrations/shai/__init__.py index c932a6f26d..941f02dbfd 100644 --- a/src/specify_cli/integrations/shai/__init__.py +++ b/src/specify_cli/integrations/shai/__init__.py @@ -36,13 +36,15 @@ def build_exec_args( integration_options: Mapping[str, Any] | None = None, project_root: Path | None = None, ) -> list[str] | None: - # SHAI has no argv form that runs a prompt. Its CLI collects every - # argument, `-p` and `--model` included, as text for its auto-fix - # agent (`trailing_var_arg`/`allow_hyphen_values` in - # shai-cli/src/main.rs of ovh/shai), which then exits 0, so the - # inherited `shai -p ` let workflow steps report success - # without running the command (#2416). Headless prompts are read from - # stdin only, and `.shai/commands` is never loaded, so opt out of - # CLI dispatch and let those steps fail instead. + # SHAI takes headless prompt text from positional arguments, stdin or + # `shai agent `, but every route goes to its auto-fix + # agent (`handle_fix` in shai-cli/src/main.rs of ovh/shai), and + # `.shai/commands` is never loaded, so no route runs an installed + # Spec Kit command. Positional arguments collect `-p` and `--model` + # too (`trailing_var_arg`/`allow_hyphen_values`), so the inherited + # `shai -p ` handed that literal text to the auto-fix agent, + # which exits 0, and workflow steps reported success without running + # the command (#2416). Opt out of CLI dispatch and let those steps + # fail instead. self.validate_runtime_config(integration_args, integration_options) return None diff --git a/tests/integrations/test_integration_shai.py b/tests/integrations/test_integration_shai.py index 8faf166415..0791fd716f 100644 --- a/tests/integrations/test_integration_shai.py +++ b/tests/integrations/test_integration_shai.py @@ -18,11 +18,12 @@ class TestShaiIntegration(MarkdownIntegrationTests): class TestShaiCliDispatch: - """SHAI has no argv form that runs a prompt (#2416). + """SHAI's CLI can't run an installed Spec Kit command (#2416). - Its CLI treats every argument, `-p` included, as text for its auto-fix - agent and exits 0, so dispatching `shai -p ` marked workflow steps - completed without running the command. + Its argument, stdin and `shai agent ` routes all hand the + text to its auto-fix agent, which exits 0, and `.shai/commands` is never + loaded, so dispatching `shai -p ` marked workflow steps completed + without running the command. """ def test_build_exec_args_opts_out(self): From f05955190bdf9d69c7bde0466e1999f5dfad2cba Mon Sep 17 00:00:00 2001 From: kartsan03 Date: Wed, 30 Sep 2026 14:51:21 +0000 Subject: [PATCH 3/4] fix(workflows): give separate remedies when a step cannot dispatch The command and prompt step errors cover both a missing CLI and an integration that does not support CLI dispatch, but the command step only suggested installing the CLI or checking `specify integration list`, which does not help for SHAI. Suggest installing the CLI when it is missing, and switching the step's integration when the integration cannot dispatch. Refs #2416 Assisted-by: Claude Code (model: claude-opus-5-5, autonomous) --- src/specify_cli/workflows/step/command/__init__.py | 5 ++++- src/specify_cli/workflows/step/prompt/__init__.py | 5 ++++- tests/integrations/test_integration_shai.py | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/specify_cli/workflows/step/command/__init__.py b/src/specify_cli/workflows/step/command/__init__.py index ebe1f36292..eb719d9cd4 100644 --- a/src/specify_cli/workflows/step/command/__init__.py +++ b/src/specify_cli/workflows/step/command/__init__.py @@ -191,7 +191,10 @@ def execute(self, config: dict[str, Any], context: StepContext) -> StepResult: f"Cannot dispatch command {command!r}: " f"integration {integration!r} CLI not found or not installed, " f"or the integration does not support CLI dispatch. " - f"Install the CLI tool or check 'specify integration list'." + f"If the CLI is missing, install it or check " + f"'specify integration list'; if the integration does not " + f"support CLI dispatch, set the step's 'integration' to one " + f"that does." ), ) diff --git a/src/specify_cli/workflows/step/prompt/__init__.py b/src/specify_cli/workflows/step/prompt/__init__.py index c0568c9258..4cfb16fb0e 100644 --- a/src/specify_cli/workflows/step/prompt/__init__.py +++ b/src/specify_cli/workflows/step/prompt/__init__.py @@ -138,7 +138,10 @@ def execute(self, config: dict[str, Any], context: StepContext) -> StepResult: f"Cannot dispatch prompt: " f"integration {integration!r} " f"CLI not found or not installed, " - f"or the integration does not support CLI dispatch." + f"or the integration does not support CLI dispatch. " + f"If the CLI is missing, install it; if the integration " + f"does not support CLI dispatch, set the step's " + f"'integration' to one that does." ), ) diff --git a/tests/integrations/test_integration_shai.py b/tests/integrations/test_integration_shai.py index 0791fd716f..bc0af99dd5 100644 --- a/tests/integrations/test_integration_shai.py +++ b/tests/integrations/test_integration_shai.py @@ -45,6 +45,7 @@ def test_command_step_fails_instead_of_running_shai(self, tmp_path): assert result.status == StepStatus.FAILED assert result.output["dispatched"] is False assert "does not support CLI dispatch" in result.error + assert "set the step's 'integration'" in result.error run.assert_not_called() def test_prompt_step_fails_instead_of_running_shai(self, tmp_path): @@ -54,4 +55,5 @@ def test_prompt_step_fails_instead_of_running_shai(self, tmp_path): assert result.status == StepStatus.FAILED assert result.output["dispatched"] is False assert "does not support CLI dispatch" in result.error + assert "set the step's 'integration'" in result.error run.assert_not_called() From 51c2de2f53eef0850d08319b8969fcbabb0afd03 Mon Sep 17 00:00:00 2001 From: kartsan03 Date: Wed, 30 Sep 2026 18:10:51 +0000 Subject: [PATCH 4/4] test(shai): fix the route wording in the dispatch test docstring Assisted-by: Claude Code (model: claude-opus-5-5, autonomous) --- tests/integrations/test_integration_shai.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integrations/test_integration_shai.py b/tests/integrations/test_integration_shai.py index bc0af99dd5..24f786e967 100644 --- a/tests/integrations/test_integration_shai.py +++ b/tests/integrations/test_integration_shai.py @@ -20,10 +20,10 @@ class TestShaiIntegration(MarkdownIntegrationTests): class TestShaiCliDispatch: """SHAI's CLI can't run an installed Spec Kit command (#2416). - Its argument, stdin and `shai agent ` routes all hand the - text to its auto-fix agent, which exits 0, and `.shai/commands` is never - loaded, so dispatching `shai -p ` marked workflow steps completed - without running the command. + SHAI's argument, stdin and `shai agent ` routes all pass + the text to its auto-fix agent, which exits 0, and `.shai/commands` is + never loaded, so dispatching `shai -p ` marked workflow steps + completed without running the command. """ def test_build_exec_args_opts_out(self):