[FIX] load_and_validate fail-open on steps-less YAML recipes#4075
Merged
Trecek merged 4 commits intoJun 11, 2026
Merged
Conversation
Restructure load_and_validate so validity is derived unconditionally from the validation pipeline output (matching the fail-closed pattern used by validate_from_path). Previously, valid was initialized to True and the validation pipeline was gated behind a "steps" in data check, causing steps-less YAML recipes to bypass all validation and be served as valid. Changes: - _api.py: change valid = True to valid = False (fail-closed default) - _api.py: remove the "steps" in data guard so the validation pipeline runs unconditionally on any successfully-parsed dict - _api.py: remove the dead else branch (now unreachable) - io.py: add type guard in _parse_recipe for non-dict steps values (converts AttributeError into clean ValueError) - tools_kitchen.py: get_recipe MCP resource returns error JSON for invalid recipes instead of raw content - tools_recipe.py: load_recipe tool includes validation_failed flag when the recipe fails validation - tests: add 9 regression tests covering steps-less YAML, cached results, sentinel-based structural guarantee, empty steps, non-dict root, non-dict steps value, campaign recipes, and downstream server-side guards Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add exist_ok=True to _setup_project_recipe for multi-recipe tests - Use RecipeInfo directly for non-dict root/steps tests (undiscoverable YAMLs) - Fix campaign dispatch YAML (gate conflicts with recipe) - Refocus campaign test on structural step errors, not semantic validity - Update schema version allowlist for shifted json.dumps line numbers - Bump tools_kitchen.py line limit to 1160 for validity guard addition - Update get_recipe test to accept validation errors (recipe still found) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ject_dir The assertion 'assert "\"error\"" not in result' was removed in 05bda5b without replacement. The new test_recipe_resource_returns_error_for_invalid_recipe covers the error path via mock but not the happy-path real-recipe assertion. Restore the error-free check to prevent regressions where get_recipe returns both the recipe name and an error key simultaneously. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The test recipe's stop message 'done' triggers the error-severity all-dispatchable-stops-have-sentinel rule, causing get_recipe to return an error instead of valid content. Update the stop message to include 'sentinel' so the recipe passes validation and the restored error-free assertion holds. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
05bda5b to
7b58ff0
Compare
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
load_and_validateinsrc/autoskillit/recipe/_api.pyinitializesvalid = True(line 315) before a conditional block that gates the entire validation pipeline behind"steps" in data(line 333). When a recipe YAML parses to a dict without astepskey, the validation pipeline is completely bypassed and the recipe is served asvalid=Truewith empty errors and suggestions. All downstream consumers (open_kitchen, fleet dispatch,load_recipetool,get_reciperesource) trust this result and serve the unvalidated recipe.The architectural fix restructures
load_and_validateso that validity is derived unconditionally from the validation pipeline output (matching the fail-closed pattern already used byvalidate_from_pathin_api_listing.py), and adds a structural test that makes it impossible for any code path throughload_and_validateto returnvalid=Truewithoutcompute_recipe_validityhaving been called.Requirements
(empty — no issue requirements section found)
Architecture Impact
No architecture diagrams were generated for this fix (no lenses succeeded).
Implementation Plan
Plan file:
/home/talon/projects/autoskillit-runs/remediation-20260611-131720-458826/.autoskillit/temp/rectify/rectify_load_and_validate_fail_open_2026-06-11_131720.mdCloses #4063
🤖 Generated with Claude Code via AutoSkillit
Token Usage Summary
* Step used a non-Anthropic provider; caching behavior may differ.
Token Efficiency
Model Usage Breakdown