Skip to content

Resolve agent CLI executables in one place so checks match dispatch - #3748

Open
dhruv-15-03 wants to merge 6 commits into
github:mainfrom
dhruv-15-03:feat/2558-cli-executable-detection
Open

dhruv-15-03 wants to merge 6 commits into
github:mainfrom
dhruv-15-03:feat/2558-cli-executable-detection

Conversation

@dhruv-15-03

@dhruv-15-03 dhruv-15-03 commented Jul 26, 2026 •

Copy link
Copy Markdown
Contributor

Problem

check_tool() decided whether an agent CLI was installed using per-tool special cases for claude, kiro-cli, rovodev and docker-agent. Dispatch did not use any of them. IntegrationBase.build_exec_args() calls _resolve_executable(), which returns the integration key unless an environment variable overrides it.

So the two paths could disagree, and on real machines they did:

  • Claude installed by claude migrate-installer or the npm local installer lives under ~/.claude/local and is not on PATH. check_tool("claude") looked there and reported it available. _resolve_executable() returned "claude", so dispatch searched PATH and failed.
  • A machine carrying only the legacy kiro binary passed check_tool("kiro-cli"), because that check accepted either name. Dispatch resolved "kiro-cli" and failed.

In both cases the preflight check says the tool is present and the run then fails to launch it.

Closes #2558.

What changed

Resolution moved onto the integrations, so the check and dispatch read one value instead of maintaining two rule sets that drift.

  • ClaudeIntegration._resolve_executable() falls back to the known local install paths when the key is not on PATH. An explicit env override, or a real PATH install, still wins.
  • KiroCliIntegration._resolve_executable() falls back to the legacy kiro binary on the same terms.
  • IntegrationBase.is_cli_available() resolves the executable, then checks that path directly when it contains a separator, or looks the bare name up on PATH.
  • DockerAgentIntegration.is_cli_available() overrides that, because Docker Agent is a docker CLI plugin rather than an executable on PATH. It uses the existing docker_agent_command() probe.
  • check_tool() asks the integration when one is registered, and keeps the plain PATH lookup for non-integration tools such as git.

RovodevIntegration already overrode _resolve_executable() to return "acli", so dropping its special case from check_tool() is behaviour-preserving. It is the pattern the other two now follow.

The two workflow dispatch sites are not touched. They already do shutil.which(exec_args[0]) and substitute the result into argv, and exec_args[0] now carries the resolved executable, so correcting resolution fixes dispatch there without editing it.

Rebase and scope reduction

This branch had gone stale. main has since gained IntegrationBase._resolve_executable() independently, which is the hook this change needs, so the cli_executable property the PR originally added is no longer necessary. I reset the branch onto current main and rebuilt the change on top of what is already there. The diff is smaller as a result: no new property, only overrides of the existing one.

That reset also removed the dispatch-site migration I had pushed earlier in review. I have not restored it, and I have explained why on that thread rather than dropping it silently.

Testing

Python 3.12.9 on Windows, at 4d2ca34 with a clean tree.

pytest tests/integrations/test_base.py tests/specify_cli/test_check_tool.py -q
98 passed, 1 skipped in 3.15s

Two new tests in tests/integrations/test_base.py assert the property that was broken: a tool reported as available must yield an argv[0] that exists. One covers the Claude local install, one the legacy kiro binary. Each asserts check_tool(...), _resolve_executable() and build_exec_args(...)[0] together, so it is the dispatch argv that is being checked, not just a helper.

They fail without the source change. With only the tests applied:

git stash push -- src
pytest tests/integrations/test_base.py -k TestCliAvailabilityMatchesDispatch -q
2 failed    assert resolved == str(local_claude)
            assert resolved == "kiro"
git stash pop
2 passed

The 12 existing cases in tests/specify_cli/test_check_tool.py are unmodified and still pass. They are the contract for the behaviour being refactored.

ruff check on the touched files reports 22 findings on main and 21 with this change, so this introduces none. The earlier claim in this description that ruff was clean on the changed files was wrong; those files are not clean on main either.

I did not run the full suite to completion. It did not finish in about 35 minutes here, and neither did tests/integrations and tests/specify_cli in full, so the slow tests are not confined to one file. They appear to block on reaching the network from this machine. I would rather say that than quote a number I did not observe.

AI disclosure

This PR was authored autonomously by an AI coding agent (GitHub Copilot, Claude Sonnet 5) operating under my supervision via the dhruv-15-03 account, per this repo's disclosure guidelines in AGENTS.md.

@dhruv-15-03
dhruv-15-03 requested a review from mnriem as a code owner July 26, 2026 07:02
Copilot AI balanced review requested due to automatic review settings July 26, 2026 07:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Medium

Comment thread src/specify_cli/integrations/base.py Outdated

@mnriem mnriem left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback

dhruv-15-03 added a commit to dhruv-15-03/spec-kit that referenced this pull request Jul 30, 2026
…able()

Both CommandStep._try_dispatch and PromptStep._try_dispatch reimplemented
CLI detection as shutil.which(impl.key) with a shutil.which(exec_args[0])
fallback, bypassing the IntegrationBase.is_cli_available() contract added
for issue github#2558. This meant Claude's non-PATH local installs
(~/.claude/local/claude, npm-local) and Kiro's legacy binary name were
invisible at these two dispatch sites even though check_tool() already
honored them.

Migrate both sites to call impl.is_cli_available() directly, matching the
pattern already used in check_tool(). Update the ~15 existing tests that
patched shutil.which at the old module paths (specify_cli.workflows.steps.
command/prompt) to patch specify_cli.integrations.base.shutil.which instead,
and add a focused regression test per dispatch site covering the Claude
non-PATH local-install scenario the Copilot review comment on this PR
flagged as unmet.

Addresses maintainer review feedback on PR github#3748:
github#3748 (comment)

Assisted-by: GitHub Copilot (model: claude-sonnet-5, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 30, 2026 16:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Comments suppressed due to low confidence (4)

src/specify_cli/integrations/claude/init.py:72

  • is_cli_available() returns True if the local-path file exists, even if it is not executable. That can produce false positives (tool reported available, but dispatch fails with OSError). Consider checking executability (e.g., os.access(path, os.X_OK)) or using shutil.which(str(path)) for these absolute paths so the contract matches “runnable CLI” rather than “file exists”.
    def is_cli_available(self) -> bool:
        """Claude Code can be installed in two local paths that may not be
        on the system ``PATH``:

          1. ``~/.claude/local/claude`` (after ``claude migrate-installer``)
          2. ``~/.claude/local/node_modules/.bin/claude`` (npm-local install,
             e.g. via nvm)

        Checked here (rather than a hardcoded special case in
        ``check_tool()``) so any future detection call site gets the same
        behavior for free. See issues #123, #550, #2558.
        """
        if _utils.CLAUDE_LOCAL_PATH.is_file() or _utils.CLAUDE_NPM_LOCAL_PATH.is_file():
            return True
        return super().is_cli_available()

tests/test_workflows.py:1308

  • This test verifies that preflight no longer blocks when PATH lookup fails, but it doesn’t assert that dispatch actually uses the local Claude executable path. To ensure the #2558 scenario is genuinely supported (not just preflight), assert on the subprocess.run call args (or whatever ultimately executes) that the executable resolved to fake_claude_local when shutil.which returns None.
    def test_dispatch_honors_claude_non_path_local_install(self, tmp_path):
        """Preflight must go through ``is_cli_available()`` so a Claude
        install at ``~/.claude/local/claude`` (not on ``PATH``, see #2558)
        is still detected — a bare ``shutil.which("claude")`` check would
        miss it and the step would wrongly report the CLI as absent."""
        from unittest.mock import MagicMock, patch
        from specify_cli.workflows.steps.command import CommandStep
        from specify_cli.workflows.base import StepContext, StepStatus

        fake_claude_local = tmp_path / "claude"
        fake_claude_local.touch()
        fake_missing = tmp_path / "nonexistent" / "claude"

        step = CommandStep()
        ctx = StepContext(
            inputs={"name": "login"},
            default_integration="claude",
            project_root=str(tmp_path),
        )
        config = {
            "id": "test",
            "command": "speckit.specify",
            "input": {"args": "{{ inputs.name }}"},
        }

        mock_result = MagicMock()
        mock_result.returncode = 0
        mock_result.stdout = '{"result": "done"}'
        mock_result.stderr = ""

        with patch("specify_cli._utils.CLAUDE_LOCAL_PATH", fake_claude_local), \
             patch("specify_cli._utils.CLAUDE_NPM_LOCAL_PATH", fake_missing), \
             patch("specify_cli.integrations.base.shutil.which", return_value=None), \
             patch("subprocess.run", return_value=mock_result):
            result = step.execute(config, ctx)

        assert result.status == StepStatus.COMPLETED
        assert result.output["dispatched"] is True

tests/test_workflows.py:1388

  • Asserting the exact number/order of shutil.which calls is brittle (internal dispatch resolution details can change without affecting behavior). Prefer asserting the key behavioral outcome (e.g., that \"/opt/claude\" was checked at least once, and that the dispatched argv[0] is \"/opt/claude\") rather than an exact seen_which list.
        # is_cli_available() resolves the override via cli_executable and
        # checks it directly — a single shutil.which("/opt/claude") call for
        # the preflight, plus dispatch_command()'s own PATHEXT resolution.
        assert seen_which == ["/opt/claude", "/opt/claude"]

src/specify_cli/integrations/kiro_cli/init.py:49

  • This duplicates the base-class detection logic. Consider return super().is_cli_available() or shutil.which(\"kiro\") is not None to keep the primary detection behavior centralized (so future changes to the default detection contract don’t need to be mirrored here).
    def is_cli_available(self) -> bool:
        """Kiro currently supports both executable names.

        Prefer ``kiro-cli`` and accept the legacy ``kiro`` binary as a
        compatibility fallback (see issue #2558).
        """
        return (
            shutil.which(self.cli_executable) is not None
            or shutil.which("kiro") is not None
        )
  • Files reviewed: 9/9 changed files
  • Comments generated: 0 new
  • Review effort level: Low

@mnriem

mnriem commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Please resolve conflicts

Copilot AI review requested due to automatic review settings August 18, 2026 17:42
@dhruv-15-03
dhruv-15-03 force-pushed the feat/2558-cli-executable-detection branch from 153ec7a to ddf2067 Compare August 18, 2026 17:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 9/9 changed files
  • Comments generated: 3
  • Review effort level: Balanced

Comment thread src/specify_cli/integrations/claude/__init__.py Outdated
Comment thread src/specify_cli/integrations/kiro_cli/__init__.py Outdated
Comment thread src/specify_cli/workflows/steps/prompt/__init__.py Outdated
@mnriem

mnriem commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback

check_tool had per-tool special cases for claude, kiro-cli, rovodev and
docker-agent. Dispatch did not use them: build_exec_args calls
_resolve_executable(), which returns the integration key unless an
environment variable overrides it. So a Claude install under
~/.claude/local, or a machine carrying only the legacy kiro binary,
passed the preflight check and then failed to launch.

The per-tool knowledge now lives on the integrations. ClaudeIntegration
and KiroCliIntegration override _resolve_executable(), so the check and
the argv dispatch builds come from the same call. IntegrationBase grows
is_cli_available(), which resolves the executable and then checks the
path directly when it contains a separator, or looks the bare name up on
PATH. DockerAgentIntegration overrides that, because it is a docker CLI
plugin rather than an executable on PATH. check_tool asks the
integration when one is registered and keeps the plain PATH lookup for
non-integration tools such as git.

Fixing resolution rather than the boolean also repairs dispatch without
editing the workflow steps: they already substitute
shutil.which(exec_args[0]) into argv, and exec_args[0] is now the
resolved path.

Two tests assert that a tool reported as available yields an argv[0]
that exists, one for the Claude local install and one for the legacy
kiro binary. Both fail without the source change.
Copilot AI review requested due to automatic review settings September 26, 2026 15:08
@dhruv-15-03
dhruv-15-03 force-pushed the feat/2558-cli-executable-detection branch from ddf2067 to 4d2ca34 Compare September 26, 2026 15:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dhruv-15-03 dhruv-15-03 changed the title Add cli_executable property to IntegrationBase for agents whose executable differs from their key Resolve agent CLI executables in one place so checks match dispatch Sep 26, 2026
@mnriem
mnriem requested a balanced review from Copilot September 28, 2026 12:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/specify_cli/integrations/base.py
Comment thread src/specify_cli/integrations/docker_agent/__init__.py Outdated
Two availability checks could report a tool as present and then fail at
dispatch -- the preflight/dispatch mismatch this branch exists to remove.

IntegrationBase.is_cli_available treated an explicit path as available on
existence alone, so a present-but-non-executable file passed preflight and
then failed at launch. The PATH branch already gets that test from
shutil.which; apply it to the explicit-path branch too.

DockerAgentIntegration.is_cli_available delegated to docker_agent_command,
which shapes argv for a custom binary without probing it, so a nonexistent
override reported available. Run the inherited check first when an override
is set; the bare key still goes through the shared probe.

Adds a regression test for each, and makes an existing claude test create an
executable stub so it still reflects an installed CLI.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 29, 2026 14:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dhruv-15-03

Copy link
Copy Markdown
Contributor Author

Addressed the Copilot feedback: fixed the two availability checks (execute bit; nonexistent docker override) with regression tests; the two older threads referenced the pre-force-push commit and are handled at head (claude, kiro-cli).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

POSIX tests currently fail, and Claude resolution can select an unusable local installation.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (4)
Previously missed (1)

In code that hasn't changed since last review

Low severity Document is_cli_available override guidance in AGENTS.md

src/​specify_cli/​integrations/​base.py:315

The linked #2558 acceptance criteria explicitly require AGENTS.md to document the is_cli_available() override mechanism, but that file still contains no mention of this hook or executable resolution. Please add the requested integration-author guidance so future integrations know when to override resolution versus availability.

Comment thread src/specify_cli/integrations/claude/__init__.py Outdated
@mnriem

mnriem commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback and fix test & lint errors

The availability check tests the execute bit, so a fixture created with
touch() alone no longer models an installed CLI on POSIX and the three
positive Claude cases failed on Linux CI.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings September 29, 2026 17:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dhruv-15-03

Copy link
Copy Markdown
Contributor Author

The three failing Claude-install fixtures now mark their fake binaries executable, matching the stricter availability check. Pushed the fixture-only fix; the CI-pinned Ruff command passes locally. Waiting for the new CI results.

The fallback loop returned the first candidate that existed, so a stale non-executable file left by one installer masked a working install later in the list: availability then rejected it on the execute bit and reported Claude as missing. Skip candidates that are not executable, matching the availability check. Adds a regression test for that ordering case and one for both candidates being unusable.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings September 30, 2026 00:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Explicit default-name overrides are not preserved, and the linked documentation criterion remains unmet.

Review effort: Balanced
Findings: None

Resolved since last review (1)
Previously missed (3)

In code that hasn't changed since last review

Medium severity Explicit default executable override incorrectly falls back to local path

src/​specify_cli/​integrations/​claude/​__init__.py:84

An explicit override equal to the default key is indistinguishable from an unset override here. For example, with SPECKIT_INTEGRATION_CLAUDE_EXECUTABLE=claude, no claude on PATH, and a local install present, this falls through and returns the local path even though the base override contract and PR description say the explicit value wins. Check whether the environment variable is set before applying local fallbacks.

Medium severity Explicit kiro-cli override incorrectly falls back to legacy kiro

src/​specify_cli/​integrations/​kiro_cli/​__init__.py:51

This comparison does not preserve an explicit override when its value is kiro-cli: if only legacy kiro is on PATH, _resolve_executable() silently replaces the requested executable with kiro. That contradicts the inherited executable-override contract and prevents operators from explicitly requiring the modern binary. Detect a nonblank SPECKIT_INTEGRATION_KIRO_CLI_EXECUTABLE value before applying the legacy fallback.

Low severity Document executable resolution and availability override mechanisms

src/​specify_cli/​integrations/​base.py:315

The linked issue's acceptance criteria explicitly require AGENTS.md to document the cli_executable/is_cli_available() override mechanism, but this new extension point is not documented there (nor in design/integration.md). Add contributor guidance explaining when integrations should override executable resolution versus availability.

@mnriem

mnriem commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator

Please address the previously missed Copilot feedback

…n key

`_resolve_executable()` collapsed "an operator pinned a binary" and "no
override is set" into a single string, so the Claude and Kiro CLI
integrations inferred "was an override set?" from `resolved != self.key`.
That inference is lossy exactly when the override equals the default key:
`SPECKIT_INTEGRATION_CLAUDE_EXECUTABLE=claude` was silently replaced by a
`~/.claude` local install, and
`SPECKIT_INTEGRATION_KIRO_CLI_EXECUTABLE=kiro-cli` by the legacy `kiro`
binary, so both ran something the operator did not ask for.

Add `IntegrationBase._executable_override()`, which returns the override or
`None`, and have both subclasses ask it instead of comparing strings.
Whitespace-only values still count as unset, and PATH, default and
no-override fallbacks are unchanged, as is the executable-candidate ordering
check. `copilot` and `rovodev` also read the override but fall back to a
different default, so the comparison is not lossy there and they are
untouched.

Adds regression tests for both default-key override cases, which fail before
this change, plus non-default and whitespace-override coverage. Documents the
resolution order and the override contract in design/integration.md.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings September 30, 2026 14:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dhruv-15-03

Copy link
Copy Markdown
Contributor Author

Addressed the three previously missed items at 1c2a2b7a.

Root cause: _resolve_executable() returned override or self.key, so claude and kiro_cli inferred "an override was set" from resolved != self.key. That inference is lossy exactly when the override equals the integration key, so SPECKIT_INTEGRATION_CLAUDE_EXECUTABLE=claude was silently replaced by the ~/.claude local install, and SPECKIT_INTEGRATION_KIRO_CLI_EXECUTABLE=kiro-cli by the legacy kiro binary.

Added IntegrationBase._executable_override(), which returns the override or None, and both subclasses now ask it instead of comparing strings. Whitespace-only values still count as unset, and PATH, default and no-override fallbacks are unchanged, as is the executable-candidate ordering check. copilot and rovodev also read the override, but each falls back to a different default, so the comparison is not lossy there; checked and left untouched.

Regression tests cover both default-key override cases and fail before this change, plus non-default and whitespace-override coverage. The resolution order and the override contract are now documented in design/integration.md, cross-referenced from AGENTS.md.

Locally: 138 passed / 4 skipped across tests/integrations/test_base.py, tests/integrations/test_extra_args.py and tests/specify_cli/test_check_tool.py; 70 passed across the Claude and Kiro CLI integration suites; the CI-pinned ruff 0.15.0 check src tests reports no findings. The new head still needs its own CI result.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Docker Agent still ignores an explicit override when its value equals the integration key.

Review effort: Balanced
Findings: 1 High severity

Open (1)

Comment on lines +81 to +82
if executable == self.key:
return docker_agent_command(None) is not None

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 50bb637.

_agent_command and is_cli_available now branch on whether _executable_override() is present instead of comparing the resolved value to self.key. SPECKIT_INTEGRATION_DOCKER_AGENT_EXECUTABLE=docker-agent is therefore treated as a real pin: it selects the standalone binary rather than the docker agent plugin form, and it no longer bypasses the inherited PATH/X_OK availability check, which the docstring already said should run.

Added four tests in tests/integrations/test_integration_docker_agent.py. The two covering command selection and availability for a default-valued override fail before the change and pass after it; unset, whitespace-only and non-default overrides keep their existing behaviour and stay covered. Focused docker_agent/base/claude/kiro_cli/copilot/rovodev/check_tool tests pass locally (311 passed, 4 skipped - the known Windows execute-bit skips), and the CI-pinned ruff@0.15.0 check src tests is clean. CI for this head has not reported yet.

I also swept the other integrations for the same override-presence comparison so an identical case isn't left behind: docker_agent was the only remaining one. copilot and rovodev override _resolve_executable too, but they never branch on whether an override is present - the resolved string is used only as argv[0] and by the base availability check - so this contract violation cannot occur there.

@mnriem

mnriem commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback

DockerAgentIntegration inferred "no override present" from
`executable == self.key`, so setting
SPECKIT_INTEGRATION_DOCKER_AGENT_EXECUTABLE=docker-agent was
indistinguishable from setting nothing: the pin was discarded in favour
of the `docker agent` plugin form, and is_cli_available() skipped the
inherited PATH/X_OK probe entirely, contradicting its own docstring.

Both call sites now branch on whether _executable_override() is present
rather than on the resolved value, matching the claude and kiro_cli
integrations. Unset, whitespace-only and non-default overrides keep
their existing behaviour.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings September 30, 2026 18:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add cli_executable property to IntegrationBase for agents whose executable differs from their key

3 participants