fix(extensions): ignore non-mapping extension.yml config section - #4323
Noor-ul-ain001 wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
🟢 Approval recommended
The focused defensive fix is correct and comprehensively tested.
Pull request overview
Prevents malformed extension manifest configuration from crashing config and hook resolution.
Changes:
- Safely ignores non-mapping
configandconfig.defaultsvalues. - Adds regression coverage for malformed and valid manifest configurations.
File summaries
| File | Description |
|---|---|
src/specify_cli/extensions/__init__.py |
Validates manifest configuration shapes before reading defaults. |
tests/test_extensions.py |
Tests malformed configuration and hook evaluation behavior. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
mnriem
left a comment
There was a problem hiding this comment.
Please fix test & lint errors
880dec3 to
11c344e
Compare
|
@mnriem I looked into the CI failure on the last run before pushing anything.
The branch was also 87 commits behind |
|
Your explanation of the earlier CI failure checks out: lint passed, and the same PowerShell timeout occurred on We’ll keep this as Please also add a plain AI-disclosure statement identifying the tool, mode/settings, and extent of assistance. The Claude Sonnet 5 attribution is already present; a session link is not needed. Drafted for @mnriem with assistance from GitHub Copilot (model: GPT-6 Astra; interactive comment drafting). |
ConfigManager._get_extension_defaults() read extension.yml's config.defaults
via manifest_data.get("config", {}).get("defaults", {}) with no shape check
on the intermediate "config" value. A manifest with `config: []` or
`config: "oops"` (the top-level config.defaults field used by shipped
extensions like extensions/git/extension.yml, distinct from the already-
validated provides.config list) made the chained .get() raise a bare
AttributeError instead of degrading like every other malformed config
source in this class. The crash was silently swallowed by
should_execute_hook's blanket except, so a hook's `config.x is set`
condition permanently evaluated to False for the extension with no
diagnostic.
This does not reject the manifest: the extension still loads and every
other manifest-driven feature (commands, hooks, skills) keeps working. Only
the malformed `config`/`config.defaults` section is ignored, falling back
to no defaults, mirroring TestConfigManagerNonMappingYaml's existing
coverage for a non-mapping *root* of <id>-config.yml, one level deeper in
the manifest's own `config` section, which was previously unchecked.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
11c344e to
3984fc5
Compare
|
Thanks for the review! Addressed both points:
Happy to leave this as |
Summary
ConfigManager._get_extension_defaults()read an extension'sextension.ymlconfig.defaultsviamanifest_data.get("config", {}).get("defaults", {})with no shape check on the intermediateconfigvalue. Shipped extensions (e.g.extensions/git/extension.yml) use a top-levelconfig: { defaults: {...} }section — distinct from the already-validatedprovides.configlist. A manifest withconfig: []orconfig: "oops"made the chained.get()raise a bareAttributeErrorinstead of degrading gracefully like every other malformed config source in this class.should_execute_hook's blanketexcept Exception, so a hook'sconfig.x is setcondition permanently evaluated toFalsefor the extension with no diagnostic — a hook that should run (or should raise a clear error) instead silently never fires.TestConfigManagerNonMappingYamlcoverage for a non-mapping root of<id>-config.yml(already fixed), one level deeper in the manifest's ownconfigsection, which was previously unchecked.ExtensionManifest._validate()does not validate this top-levelconfigfield at all.This is not a manifest rejection. The fix does not refuse to load, or error out on, a manifest with a malformed
configsection — the extension still loads normally and every other manifest-driven feature (commands, hooks, skills) keeps working. Only the malformedconfig/config.defaultssection itself is ignored, falling back to{}(no defaults), the same graceful-degradation behavior already used by every other malformed config source inConfigManager.Test plan
TestConfigManagerNonMappingManifestConfigSectiontotests/test_extensions.py(5 tests: listconfig:, scalarconfig:, non-mappingconfig.defaults, valid-shape regression guard, and aHookExecutor._evaluate_conditionnon-crash check)AttributeError: 'list'/'str' object has no attribute 'get') and pass with it — stashed only the source change (src/specify_cli/extensions/__init__.py), confirmed the failure, then restored it and confirmed the passtests/test_extensions.pyfull suite — 505 passed, 20 pre-existing failures (Windows symlink-elevation and env-specific; reproduced identically on the unmodified branch), 8 skipped — no regressionsAI Disclosure
This PR was authored with Claude Code (model: Claude Sonnet 5), used in its interactive, agentic CLI mode with full local tool access (file read/edit, running
pytest,git). Extent of assistance: the AI agent identified the bug, wrote the source fix and all 5 regression tests, and ran the test-the-test verification and full suite described above, under my direction. I reviewed the resulting diff and test output myself before opening this PR and understand the change being made.Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com