Repository navigation
Conversation
ConfigManager._get_extension_defaults read only `config.defaults`, while the extension template, EXTENSION-API-REFERENCE.md and EXTENSION-DEVELOPMENT-GUIDE.md document a top-level `defaults:` key. Defaults declared the documented way were silently dropped, so hook conditions such as `config.feature.enabled == 'true'` treated them as unset. Read both keys and merge them, with `config.defaults` winning on conflicting keys, so manifests that use it (including the bundled git extension) resolve exactly as before. A non-mapping value under either key is ignored, as a malformed `config` section already is. Update the precedence list in design/extension.md to match. Fixes github#5084 Assisted-by: Claude Code (model: Claude Opus 5.5, autonomous) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Tests omit non-mapping config and config.defaults cases with valid top-level defaults, leaving regressions that discard those defaults undetected.
1 open issue
What changed in this PR
Fixes #5084 by loading documented top-level defaults from extension.yml, while preserving precedence for existing config.defaults.
Changes:
- Merge both default mappings and ignore non-mapping values.
- Add regression tests for defaults loading, precedence, project overrides, and hook conditions.
- Update the documented configuration precedence.
Validation relied on code inspection and the supplied regression evidence; tests were not rerun here.
| File | Description |
|---|---|
tests/test_extensions.py |
Adds regression coverage for top-level manifest defaults. |
src/specify_cli/extensions/__init__.py |
Loads and merges both manifest default mappings. |
design/extension.md |
Documents both default sources and their precedence. |
Review effort: Max
|
Please address Copilot feedback |
Cover a valid top-level `defaults:` next to a non-mapping `config` or
`config.defaults` (list and scalar), asserting the top-level defaults
survive. Each pair fails if the matching fallback in
_get_extension_defaults returns {} instead of the top-level defaults.
Assisted-by: Claude Code (model: Claude Opus 5.5, autonomous)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review round 1, commit ba091cc: added AI disclosure: posted on behalf of joshinitin2016883-sketch by Claude Code (model: Claude Opus 5.5), running autonomously with default settings. The agent wrote the test, ran the checks above and wrote this comment; there was no human line-by-line review. |
There was a problem hiding this comment.
🔵 Needs a closer look
Maintainer confirmation is needed because the change prioritizes config.defaults over top-level defaults and alters community-extension hook conditions while issue #5084 leaves the contract direction open.
No open issues 🎉
1 resolved since last review
Review effort: Max
|
Thank you! |

Description
Fixes #5084.
ConfigManager._get_extension_defaultsreads defaults only fromconfig.defaultsinextension.yml. The extension template (extensions/template/extension.yml),EXTENSION-API-REFERENCE.mdandEXTENSION-DEVELOPMENT-GUIDE.mdall document a top-leveldefaults:key instead, which was silently ignored. As a result,get_config()/get_value()and hook conditions such asconfig.feature.enabled == 'true'treated those defaults as unset.The issue notes that either side could change. I fixed it in the code rather than the docs, because published extensions follow the docs. In a sample of 22 community
extension.ymlfiles found via GitHub code search, 19 declare a top-leveldefaults:and 3 useconfig.defaults. Changing the docs toconfig.defaultswould leave most existing extensions with ignored defaults; reading both keys fixes them without breaking anyone.This PR:
_get_extension_defaults: reads the top-leveldefaultsmapping as well asconfig.defaults, and merges them with the existing_merge_configs.config.defaultswins on conflicting keys, so manifests that already use it, including the bundled git extension, resolve exactly as before. A non-mapping value under either key is ignored, the same way a malformedconfigsection already is.design/extension.md: the precedence list now names both keys.The template, API reference and development guide already describe the top-level form, so they need no change.
Testing
Evidence was gathered on Windows 11 with Python 3.14.
Reproduction: the script from #5084, run on
mainand on this branch, plus a third case that loads the shippedextensions/template/extension.yml:maindefaults:{}{'feature': {'enabled': True}}config: defaults:{'feature': {'enabled': True}}{'feature': {'enabled': True}}{}{'feature': {'enabled': True, 'auto_sync': False}}Regression tests in
TestConfigManagerTopLevelManifestDefaults(tests/test_extensions.py):test_top_level_defaults_load,test_shipped_template_defaults_load,test_config_defaults_win_on_conflict,test_project_config_merges_over_top_level_defaults,test_hook_condition_sees_top_level_default: fail onmain, pass with this change.test_non_mapping_top_level_defaults_is_ignored: passes before and after, guarding the malformed-shape path.TestConfigManagerNonMappingManifestConfigSectionandTestConfigManagerNonMappingYamltests still pass.bashto Git Bash andpwshto Windows PowerShell 5.1: 12,948 passed, 265 failed, 437 skipped. Re-running the 45 files containing those failures onmaingives the identical 265 failures (Windows-specific: symlinks, path handling, shell parity), so this change adds no failures.ruff check src testswith ruff 0.15.0 (as in CI) passes.Sample project:
specify init … --integration claude --non-interactive, thenspecify extension add --devof a copy ofextensions/templatewhoseafter_taskshook hascondition: "config.feature.enabled == 'true'"(feature.enabledcomes only from the manifest's top-leveldefaults:). ThenConfigManagerandHookExecutor.check_hooks_for_event("after_tasks")in that project:get_value("feature.enabled")after_taskshookmainNone# No executable hooks for event 'after_tasks' (conditions not met)Truespecify --help(from a venv install, withoutuv)Manual slash-command testing: no slash command changes. The affected path is the CLI's extension config and hook condition evaluation, verified above through
specify extension addandHookExecutor, rather than through an agent session.AI Disclosure
AI disclosure: Claude Code (model: Claude Opus 5.5), running autonomously with default settings. I chose to work on this issue and approved each publishing step, but did not review every line myself. Extent: the agent surveyed community extensions, wrote the code, tests and this description, and ran the verification above on my Windows machine.
🤖 Generated with Claude Code