Skip to content

fix(extensions): load top-level defaults from extension.yml - #5164

Merged
mnriem merged 2 commits into
github:mainfrom
joshinitin2016883-sketch:fix/5084-top-level-extension-defaults
Oct 28, 2026
Merged

mnriem merged 2 commits into
github:mainfrom
joshinitin2016883-sketch:fix/5084-top-level-extension-defaults

Conversation

@joshinitin2016883-sketch

Copy link
Copy Markdown
Contributor

Description

Fixes #5084.

ConfigManager._get_extension_defaults reads defaults only from config.defaults in extension.yml. The extension template (extensions/template/extension.yml), EXTENSION-API-REFERENCE.md and EXTENSION-DEVELOPMENT-GUIDE.md all document a top-level defaults: key instead, which was silently ignored. As a result, get_config() / get_value() and hook conditions such as config.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.yml files found via GitHub code search, 19 declare a top-level defaults: and 3 use config.defaults. Changing the docs to config.defaults would leave most existing extensions with ignored defaults; reading both keys fixes them without breaking anyone.

This PR:

  • _get_extension_defaults: reads the top-level defaults mapping as well as config.defaults, and merges them with the existing _merge_configs. config.defaults wins 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 malformed config section 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 main and on this branch, plus a third case that loads the shipped extensions/template/extension.yml:

Manifest main this branch
top-level defaults: {} {'feature': {'enabled': True}}
config: defaults: {'feature': {'enabled': True}} {'feature': {'enabled': True}}
shipped template {} {'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 on main, pass with this change.
  • test_non_mapping_top_level_defaults_is_ignored: passes before and after, guarding the malformed-shape path.
  • The existing TestConfigManagerNonMappingManifestConfigSection and TestConfigManagerNonMappingYaml tests still pass.
  • Full suite on this Windows machine, with a local-only pytest plugin that maps bash to Git Bash and pwsh to Windows PowerShell 5.1: 12,948 passed, 265 failed, 437 skipped. Re-running the 45 files containing those failures on main gives the identical 265 failures (Windows-specific: symlinks, path handling, shell parity), so this change adds no failures.
  • ruff check src tests with ruff 0.15.0 (as in CI) passes.

Sample project: specify init … --integration claude --non-interactive, then specify extension add --dev of a copy of extensions/template whose after_tasks hook has condition: "config.feature.enabled == 'true'" (feature.enabled comes only from the manifest's top-level defaults:). Then ConfigManager and HookExecutor.check_hooks_for_event("after_tasks") in that project:

get_value("feature.enabled") after_tasks hook
main None # No executable hooks for event 'after_tasks' (conditions not met)
this branch True returned as executable
  • Tested locally with specify --help (from a venv install, without uv)
  • Ran the existing tests with pytest (details above)
  • Tested with a sample project

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 add and HookExecutor, rather than through an agent session.

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (fill in the disclosure below)

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

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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

Comment thread tests/test_extensions.py
@mnriem

mnriem commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator

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>
@joshinitin2016883-sketch

Copy link
Copy Markdown
Contributor Author

Review round 1, commit ba091cc: added test_top_level_defaults_survive_non_mapping_config, parametrized over a list or scalar config and a list or scalar config.defaults next to a valid top-level defaults:, asserting that the top-level defaults are kept. To check it, I changed each fallback in _get_extension_defaults to return {}: each change fails its two matching cases, and all four cases fail on main. tests/test_extensions.py passes except the two symlink scaffolding tests that also fail on main on Windows; ruff 0.15.0 is clean.

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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

@mnriem
mnriem merged commit f070a70 into github:main Oct 28, 2026
16 checks passed
@mnriem

mnriem commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

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.

[Bug]: Top-level defaults: in extension.yml, as documented and used in the extension template, is ignored by ConfigManager

3 participants