Skip to content

feat(commands): register generated Goose recipes as slash commands - #2626

Merged
dyoshikawa merged 3 commits into
mainfrom
resolve-issue-2404-goose-slash-commands
Aug 7, 2026
Merged

feat(commands): register generated Goose recipes as slash commands#2626
dyoshikawa merged 3 commits into
mainfrom
resolve-issue-2404-goose-slash-commands

Conversation

@dyoshikawa

Copy link
Copy Markdown
Owner

Goose recipes do not auto-register: /name only resolves once the recipe is listed under slash_commands in the user config, so a generated recipe was uninvocable without hand-editing ~/.config/goose/config.yaml.

Verified upstream before implementing: the entries are SlashCommandMapping { command, recipe_path } (https://github.com/block/goose/blob/main/crates/goose/src/slash_commands/recipe_slash_command.rs), and recipe_path goes through tilde/relative expansion via convert_path_with_tilde_expansion in crates/goose/src/recipe/read_recipe_file_content.rs — so the registration is written as ~/.config/goose/recipes/<name>.yaml rather than a machine-specific absolute path.

Implementation:

  • GooseCommand.getAuxiliaryFiles emits the user config.yaml in global mode only (upstream has no project-level registration surface; project recipes still need goose run --recipe).
  • The gateway declaration for .config/goose/config.yaml gains commands: replace-owned-keys over slash_commands, alongside the existing mcp ownership of extensions.
  • Ownership is narrow, per the Hermes lesson: only entries whose recipe_path resolves to a direct child of the managed recipes directory are rulesync's. User entries elsewhere — and sub-recipes under recipes/subagents/ — are carried over untouched, and an unresolvable path is preserved rather than claimed.
  • Removing the last generated command retracts the registrations and drops the key; the config file is never created when there is nothing to register, and is never a deletion candidate.

Tests cover registration, project-scope no-op, preservation, stale-entry replacement, retraction and the no-op case; the shared-file-derive snapshot now pins commands+mcp as the writers of that file. e2e-commands passes locally.

Gap 1 (the Goose review-checks adapter) remains blocked on the .agents/checks path-collision ownership call.

Part of #2404

🤖 Generated with Claude Code

A Goose recipe is not invocable as /name until it is listed under slash_commands in the user config, so generated recipes needed hand-editing to be usable. Global generate now writes the registrations through the shared-config gateway, owning only entries that point into the managed recipes directory.
Goose resolves a slash command's recipe_path with a bare PathBuf::from followed by exists(): the tilde expansion used by 'goose run --recipe' is not on that path, so a ~-relative registration never resolved. Write the absolute path instead, lowercase the command name (Goose compares the stored value verbatim against the lowercased input), and retract the registrations from removeOrphanAiFiles so --delete cannot strand them when the feature has no source files left.
The retraction path re-serialized the config whenever it ran, so a --global --delete run with no Goose commands rewrote a user config that rulesync owned nothing in, dropping their comments. Both writers now check for a managed registration first.
@dyoshikawa
dyoshikawa merged commit 31b36a2 into main Aug 7, 2026
9 checks passed
@dyoshikawa
dyoshikawa deleted the resolve-issue-2404-goose-slash-commands branch August 7, 2026 20:08
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.

2 participants