Conversation
…t discovery
When a recipe has nested sub-recipes that use {{ recipe_dir }} in their
paths, the secret discovery phase would try to load files with the literal
unresolved template string as the path. This caused confusing output like:
📦 Looking for recipe "{{ recipe_dir }}/leaf.yaml" in github repo: ...
fatal: not a valid object name: origin/main:{{ recipe_dir }}/...
The fix resolves {{ recipe_dir }} to the sub-recipe's actual parent
directory before recursing into nested sub-recipes. This eliminates the
confusing output and also correctly discovers secrets from all nesting
levels.
Uses regex::NoExpand to treat the replacement path as a literal string,
preventing $ characters in directory paths from being misinterpreted as
capture group references.
Fixes #5040
jamadeo
approved these changes
Mar 11, 2026
| parent_dir: &str, | ||
| visited_recipes: &mut HashSet<String>, | ||
| ) -> Vec<SecretRequirement> { | ||
| let re = Regex::new(r"\{\{\s*recipe_dir\s*\}\}").expect("valid regex"); |
Collaborator
There was a problem hiding this comment.
This could be put in a OnceCell so it isn't compiled every time
Collaborator
Author
There was a problem hiding this comment.
yes, but is it worth the mental overhead? this doesn't happen very often at all
lifeizhou-ap
added a commit
that referenced
this pull request
Mar 11, 2026
* main: (45 commits)
fix: resolve {{ recipe_dir }} in nested sub-recipe paths during secret discovery (#7797)
Add @DOsinga as CODEOWNER for documentation (#7799)
feat: Add summarize tool for deterministic reads (#7054)
fix(api): use camelCase in CallToolResponse and add type discriminators to ContentBlock (#7487)
feat: ACP providers for claude code and codex (#6605)
chore(deps): bump express-rate-limit from 8.2.1 to 8.3.0 in /evals/open-model-gym/mcp-harness (#7703)
feat(openai): capture reasoning summaries from responses API (#7375)
Fix some dependencies (#7794)
fix: improve keyring availability error detection (#7766)
feat: add MiniMax provider with Anthropic-compatible API (#7640)
feat: add Tensorix as a declarative provider (#7712)
fix(security): remove insecure default secret from GOOSE_EXTERNAL_BACKEND (#7783)
refactor: Convert Tanzu provider to declarative JSON config (#7124)
replaces https://github.com/block/goose/pull/7340/changes (#7786)
feat(summon): make skill supporting files individually loadable via load() (#7583)
Keep toast open on failed extension (#7771)
fix(ui-desktop): unify path resolution around GOOSE_PATH_ROOT (#7335)
fix: pass OAuth scopes to DCR and extract granted_scopes from token response (#7571)
fix: write to real file if config.yaml is symlink (#7669)
fix: preserve pairings when stopping gateway (#7733)
...
lifeizhou-ap
added a commit
that referenced
this pull request
Mar 11, 2026
* main: (69 commits)
fix: resolve {{ recipe_dir }} in nested sub-recipe paths during secret discovery (#7797)
Add @DOsinga as CODEOWNER for documentation (#7799)
feat: Add summarize tool for deterministic reads (#7054)
fix(api): use camelCase in CallToolResponse and add type discriminators to ContentBlock (#7487)
feat: ACP providers for claude code and codex (#6605)
chore(deps): bump express-rate-limit from 8.2.1 to 8.3.0 in /evals/open-model-gym/mcp-harness (#7703)
feat(openai): capture reasoning summaries from responses API (#7375)
Fix some dependencies (#7794)
fix: improve keyring availability error detection (#7766)
feat: add MiniMax provider with Anthropic-compatible API (#7640)
feat: add Tensorix as a declarative provider (#7712)
fix(security): remove insecure default secret from GOOSE_EXTERNAL_BACKEND (#7783)
refactor: Convert Tanzu provider to declarative JSON config (#7124)
replaces https://github.com/block/goose/pull/7340/changes (#7786)
feat(summon): make skill supporting files individually loadable via load() (#7583)
Keep toast open on failed extension (#7771)
fix(ui-desktop): unify path resolution around GOOSE_PATH_ROOT (#7335)
fix: pass OAuth scopes to DCR and extract granted_scopes from token response (#7571)
fix: write to real file if config.yaml is symlink (#7669)
fix: preserve pairings when stopping gateway (#7733)
...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #5040
When a recipe has nested sub-recipes that use
{{ recipe_dir }}in their paths, the secret discovery phase would try to load files with the literal unresolved template string as the path. This caused confusing output like:It also meant secrets from deeply nested sub-recipes were never discovered.
Root Cause
discover_recipe_secrets_recursiveloads sub-recipe files as raw YAML (no template rendering). When those sub-recipes themselves havesub_recipesentries with{{ recipe_dir }}in the path, the recursive call uses the literal unrendered string as a file path, which fails local lookup and falls through to a confusing GitHub repository search.Fix
When recursing into a loaded sub-recipe, resolve
{{ recipe_dir }}(with any whitespace variation) to the sub-recipe file's actual parent directory before the recursive call. This is done via a newdiscover_sub_recipe_secretshelper that uses a regex replacement withNoExpandto treat the directory path as a literal string (preventing$in paths from being misinterpreted as capture group references).Testing
Validated with a 3-level recipe hierarchy (top → middle → leaf) where the middle and leaf recipes use
{{ recipe_dir }}in sub-recipe paths:Before (main): Found only 1 of 2 secrets, produced confusing GitHub lookup output for
{{ recipe_dir }}/leaf.yamlAfter (this PR): Found all 2 secrets, no confusing output
Reproduction script
Changes
crates/goose-cli/src/recipes/secret_discovery.rs:load_sub_recipenow returns the parent directory alongside the recipediscover_sub_recipe_secretsresolves{{ recipe_dir }}before recursingregex::NoExpandfor safe literal replacement