Skip to content

fix(integrations): dispatch goose commands via goose run (#2416) - #3781

Open
jawwad-ali wants to merge 1 commit into
github:mainfrom
jawwad-ali:fix/goose-cli-dispatch
Open

fix(integrations): dispatch goose commands via goose run (#2416)#3781
jawwad-ali wants to merge 1 commit into
github:mainfrom
jawwad-ali:fix/goose-cli-dispatch

Conversation

@jawwad-ali

Copy link
Copy Markdown
Contributor

Addresses the Goose item in #2416. (Not Fixes, since that issue also tracks a broader flag audit — see Scope below.)

Problem

YamlIntegration never overrode build_exec_args(), so GooseIntegration inherited the IntegrationBase no-op that returns None. Callers read None as "this CLI is unavailable", so a workflow command/prompt step targeting Goose reported CLI not found or not installed — even with goose present on PATH.

Reproduced with the CLI on PATH (shutil.which stubbed to a real path, subprocess.run stubbed), using the same command: speckit.specify form the shipped workflows/speckit/workflow.yml uses:

amp       -> completed  argv=['amp', '-p', '/speckit.specify']
opencode  -> completed  argv=['opencode', 'run', '--command', 'speckit.specify']
goose     -> FAILED     "Cannot dispatch command 'speckit.specify': integration
                         'goose' CLI not found or not installed."

The message is simply false — goose is installed. CommandStep swallows the NotImplementedError from dispatch_command and reports the not-installed result; PromptStep silently no-ops on a falsy exec_args.

Fix

Implement build_exec_args() for Goose. Per the goose CLI docs there is no -p flag; the non-interactive entry point is goose run:

flag purpose
-t, --text <TEXT> free-form text
--recipe <RECIPE_FILE_NAME> run a stored recipe
--params <KEY=VALUE> recipe parameters
--model / --output-format <FORMAT> model + text|json|stream-json

Spec Kit already installs its commands as Goose recipes under .goose/recipes/, each declaring an optional args string parameter (enforced today by test_setup_declares_args_parameter_for_args_prompt), so a /speckit.<name> <rest> invocation maps exactly onto --recipe <path> --params args=<rest>. This mirrors OpencodeIntegration, which maps the same leading slash-command onto opencode's native --command.

'/speckit.specify add auth' -> goose run --output-format json --recipe .goose/recipes/speckit.specify.yaml --params args=add auth
'/speckit.plan'             -> goose run --output-format json --recipe .goose/recipes/speckit.plan.yaml
'/speckit.git.commit msg'   -> goose run --output-format json --recipe .goose/recipes/speckit.git.commit.yaml --params args=msg
'just do it'                -> goose run --output-format json -t just do it

Anti-drift: the recipe path is derived from the same two sources setup() uses — config["folder"] + config["commands_subdir"] (as commands_dest() does) and command_filename() — rather than registrar_config["extension"], so the dispatch target cannot diverge from the file that was actually installed. A test runs setup() and asserts the resolved --recipe path is a real file on disk.

Note on #2424

#2424 proposes the opposite — exec_mode = "none" for goose, on the grounds that it is a "recipe-based workflow". Respectfully, recipe-based execution is precisely what goose run is built for: --recipe and --params KEY=VALUE are first-class flags, and Spec Kit already generates recipes declaring the matching args parameter, so the mapping is exact rather than approximate. Happy to defer if you prefer that direction — this PR is deliberately narrow so either can land independently.

Verification

  • 7 new dispatch tests + 3 extra-args/executable parity tests: all fail before this change, pass after.
  • tests/integrations/test_integration_goose.py: 35 passed (28 pre-existing untouched).
  • tests/test_agent_config_consistency.py: 27 passedrequires_cli is deliberately not touched.
  • uvx ruff@0.15.0 check clean.
  • Flags cross-checked against the official goose CLI docs (confirmed -t/--text, --recipe, --params, --model, --output-format; confirmed no -p).

Scope

Only the Goose dispatch gap. The issue's headline opencode item was already fixed by c079b2c, and the broader "audit CLI flags for 11+ integrations" / declarative-exec_* refactor is intentionally left out — that is a larger change needing a maintainer design call.


AI-assisted: authored with Claude Code. I reproduced the false "CLI not found" result on main, verified the goose flags against the vendor docs, and added the anti-drift test tying dispatch to what setup() writes.

@jawwad-ali
jawwad-ali requested a review from mnriem as a code owner July 28, 2026 10:15
`YamlIntegration` never overrode `build_exec_args()`, so `GooseIntegration`
inherited the `IntegrationBase` no-op that returns `None`. Callers read `None`
as "this CLI is unavailable", so every workflow command/prompt step targeting
Goose reported `CLI not found or not installed` even with `goose` on PATH.

Reproduced with the agent CLI present on PATH (shutil.which stubbed to a real
path, subprocess.run stubbed):

    amp       -> completed  argv=['amp', '-p', '/speckit.specify']
    opencode  -> completed  argv=['opencode', 'run', '--command', 'speckit.specify']
    goose     -> FAILED     "integration 'goose' CLI not found or not installed"

Implement `build_exec_args()` for Goose. Per the goose CLI docs there is no
`-p` flag; the non-interactive entry point is `goose run`, which takes
`-t/--text` for free-form text, `--recipe` for a stored recipe,
`--params KEY=VALUE` for recipe parameters, plus `--model` and
`--output-format`. Spec Kit installs its commands as Goose *recipes* under
`.goose/recipes/`, each declaring an optional `args` parameter (already
enforced by test_setup_declares_args_parameter_for_args_prompt), so a
`/speckit.<name> <rest>` invocation maps exactly onto
`--recipe <path> --params args=<rest>`. This mirrors `OpencodeIntegration`,
which maps the same leading slash-command onto opencode's `--command`.

The recipe path is derived from the same two sources `setup()` uses --
`config["folder"]` + `config["commands_subdir"]` and `command_filename()` --
so the dispatch target cannot drift from the installed file; a test asserts the
resolved `--recipe` path exists after `setup()`. Dotted extension commands
(`speckit.git.commit`) round-trip. Extra args are applied before the canonical
flags so Spec Kit's selection stays authoritative, matching opencode.

No behaviour change for other integrations, and `requires_cli` is untouched.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jawwad-ali
jawwad-ali force-pushed the fix/goose-cli-dispatch branch from f8558f8 to 4d7fe7f Compare July 28, 2026 14:56
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.

1 participant