Skip to content

Commit dd5d50f

Browse files
authored
fix(integrations): stop dispatching workflow steps to SHAI (#4791)
* 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(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 <name> <prompt>`, 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) * 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) * test(shai): fix the route wording in the dispatch test docstring Assisted-by: Claude Code (model: claude-opus-5-5, autonomous)
1 parent 4a33920 commit dd5d50f

5 files changed

Lines changed: 90 additions & 4 deletions

File tree

‎docs/reference/integrations.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ The Specify CLI supports a wide range of AI coding agents. When you run `specify
4242
| [Qoder CLI](https://qoder.com/cli) | `qodercli` | |
4343
| [Qwen Code](https://github.com/QwenLM/qwen-code) | `qwen` | |
4444
| [RovoDev](https://www.atlassian.com/software/rovo-dev) | `rovodev` | Generates `.rovodev/skills/`, prompt wrappers, and `prompts.yml`; runtime dispatch uses `acli rovodev` |
45-
| [SHAI (OVHcloud)](https://github.com/ovh/shai) | `shai` | |
45+
| [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 |
4646
| [Tabnine CLI](https://docs.tabnine.com/main/getting-started/tabnine-cli) | `tabnine` | |
4747
| [Trae](https://www.trae.ai/) | `trae` | Skills-based integration; skills are installed automatically |
4848
| [ZCode](https://zcode.z.ai/) | `zcode` | Skills-based integration; installs skills into `.zcode/skills/` and invokes them as `$speckit-<command>` |

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
"""SHAI CLI integration."""
22

3+
from __future__ import annotations
4+
5+
from collections.abc import Mapping, Sequence
6+
from pathlib import Path
7+
from typing import Any
8+
39
from ..base import MarkdownIntegration
410

511

@@ -19,3 +25,26 @@ class ShaiIntegration(MarkdownIntegration):
1925
"extension": ".md",
2026
}
2127
multi_install_safe = True
28+
29+
def build_exec_args(
30+
self,
31+
prompt: str,
32+
*,
33+
model: str | None = None,
34+
output_json: bool = True,
35+
integration_args: Sequence[str] | None = None,
36+
integration_options: Mapping[str, Any] | None = None,
37+
project_root: Path | None = None,
38+
) -> list[str] | None:
39+
# SHAI takes headless prompt text from positional arguments, stdin or
40+
# `shai agent <name> <prompt>`, but every route goes to its auto-fix
41+
# agent (`handle_fix` in shai-cli/src/main.rs of ovh/shai), and
42+
# `.shai/commands` is never loaded, so no route runs an installed
43+
# Spec Kit command. Positional arguments collect `-p` and `--model`
44+
# too (`trailing_var_arg`/`allow_hyphen_values`), so the inherited
45+
# `shai -p <prompt>` handed that literal text to the auto-fix agent,
46+
# which exits 0, and workflow steps reported success without running
47+
# the command (#2416). Opt out of CLI dispatch and let those steps
48+
# fail instead.
49+
self.validate_runtime_config(integration_args, integration_options)
50+
return None

‎src/specify_cli/workflows/step/command/__init__.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,12 @@ def execute(self, config: dict[str, Any], context: StepContext) -> StepResult:
189189
output=output,
190190
error=(
191191
f"Cannot dispatch command {command!r}: "
192-
f"integration {integration!r} CLI not found or not installed. "
193-
f"Install the CLI tool or check 'specify integration list'."
192+
f"integration {integration!r} CLI not found or not installed, "
193+
f"or the integration does not support CLI dispatch. "
194+
f"If the CLI is missing, install it or check "
195+
f"'specify integration list'; if the integration does not "
196+
f"support CLI dispatch, set the step's 'integration' to one "
197+
f"that does."
194198
),
195199
)
196200

‎src/specify_cli/workflows/step/prompt/__init__.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,11 @@ def execute(self, config: dict[str, Any], context: StepContext) -> StepResult:
137137
error=(
138138
f"Cannot dispatch prompt: "
139139
f"integration {integration!r} "
140-
f"CLI not found or not installed."
140+
f"CLI not found or not installed, "
141+
f"or the integration does not support CLI dispatch. "
142+
f"If the CLI is missing, install it; if the integration "
143+
f"does not support CLI dispatch, set the step's "
144+
f"'integration' to one that does."
141145
),
142146
)
143147

‎tests/integrations/test_integration_shai.py‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
"""Tests for ShaiIntegration."""
22

3+
from unittest.mock import MagicMock, patch
4+
5+
from specify_cli.integrations import get_integration
6+
from specify_cli.workflows.base import StepContext, StepStatus
7+
from specify_cli.workflows.step.command import CommandStep
8+
from specify_cli.workflows.step.prompt import PromptStep
9+
310
from .test_integration_base_markdown import MarkdownIntegrationTests
411

512

@@ -8,3 +15,45 @@ class TestShaiIntegration(MarkdownIntegrationTests):
815
FOLDER = ".shai/"
916
COMMANDS_SUBDIR = "commands"
1017
REGISTRAR_DIR = ".shai/commands"
18+
19+
20+
class TestShaiCliDispatch:
21+
"""SHAI's CLI can't run an installed Spec Kit command (#2416).
22+
23+
SHAI's argument, stdin and `shai agent <name> <prompt>` routes all pass
24+
the text to its auto-fix agent, which exits 0, and `.shai/commands` is
25+
never loaded, so dispatching `shai -p <prompt>` marked workflow steps
26+
completed without running the command.
27+
"""
28+
29+
def test_build_exec_args_opts_out(self):
30+
integration = get_integration("shai")
31+
assert integration.build_exec_args("/speckit.plan", model="m") is None
32+
33+
def _run(self, step, config, tmp_path):
34+
ctx = StepContext(default_integration="shai", project_root=str(tmp_path))
35+
exited_ok = MagicMock(returncode=0, stdout="", stderr="")
36+
with patch("shutil.which", return_value="/usr/local/bin/shai"), \
37+
patch("subprocess.run", return_value=exited_ok) as run:
38+
result = step.execute(config, ctx)
39+
return result, run
40+
41+
def test_command_step_fails_instead_of_running_shai(self, tmp_path):
42+
result, run = self._run(
43+
CommandStep(), {"id": "plan", "command": "speckit.plan"}, tmp_path
44+
)
45+
assert result.status == StepStatus.FAILED
46+
assert result.output["dispatched"] is False
47+
assert "does not support CLI dispatch" in result.error
48+
assert "set the step's 'integration'" in result.error
49+
run.assert_not_called()
50+
51+
def test_prompt_step_fails_instead_of_running_shai(self, tmp_path):
52+
result, run = self._run(
53+
PromptStep(), {"id": "ask", "prompt": "Summarize the spec"}, tmp_path
54+
)
55+
assert result.status == StepStatus.FAILED
56+
assert result.output["dispatched"] is False
57+
assert "does not support CLI dispatch" in result.error
58+
assert "set the step's 'integration'" in result.error
59+
run.assert_not_called()

0 commit comments

Comments
 (0)