Skip to content

fix(provider): skip config rewrite when provider entry already current - #99

Merged
iap merged 4 commits into
mainfrom
fix/provider-idempotency
Aug 22, 2026
Merged

fix(provider): skip config rewrite when provider entry already current#99
iap merged 4 commits into
mainfrom
fix/provider-idempotency

Conversation

@iap

@iap iap commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Summary

Hardens the install/uninstall flow so plugin load no longer rewrites config.yaml
(and strips its comments), removes two small redundancies, and fixes a
default-model mismatch Greptile caught.

1. register_provider() no longer rewrites config on every load (H1)

The previous implementation always ran load_config() + save_config() on plugin
load, which round-trips the YAML and strips all user comments even when nothing
changed. This is now idempotent:

  • _entries_equivalent() compares the rebuilt entry against what is already stored,
    treating the "***" redaction sentinel and "no-key-required" as equivalent.
  • save_config() is skipped when the entry is already current and no legacy
    migration ran. A changed flag ensures the legacy aws-build -> aws-builder
    slug migration is still persisted even when the entry is otherwise a no-op.
  • Intentional behavior is preserved: legacy adoption, foreign-entry protection, and
    model-catalog refresh still write when there is an actual difference.
  • scripts/setup.sh now emits discover_models: false, matching the runtime path.
  • Regression test: test_provider_register_noop_when_already_current.

2. Single source of truth for the model catalog (M3)

register_provider() hardcoded a third copy of the model list as an
or [...] fallback. That fallback is unreachable in normal operation —
_declared_models() -> backend.list_models() -> STATIC_MODELS already covers the
"no plugin.yaml" case. The empty-catalog case (backend import failure) now skips
registration instead of duplicating the list.

  • Regression test: test_provider_register_skips_when_no_models.

3. Preserve declared model order in setup.sh (L7)

yaml.dump alphabetizes mapping keys by default. Pass sort_keys=False so the
generated models: block mirrors the plugin.yaml declaration order.

4. Derive default model from the declared catalog (Greptile P1)

setup.sh hardcoded model: "auto" in the generated block, so a custom
plugin.yaml catalog without auto advertised a default the provider does not
actually offer. The default now comes from models[0] — the same value
register_provider() uses at runtime — so the selected default is always present in
the provider's own models: block.

  • New tests/test_setup.py exercises the real block-generation heredoc
    (custom-catalog regression + shipped-default sanity check).

Notes / corrections from the flow analysis

  • api_key: "***" is not a bug (H2 was misdiagnosed): it is the redaction
    sentinel that hermes_cli normalizes to "no-key-required" on save. No change
    needed.
  • "Read-only register_provider()" was too blunt — it would break intentional,
    tested behavior (adoption, migration, foreign-entry protection). Change-detection
    achieves the same comment-preservation outcome without that collateral.

Deferred (documented, not in this PR)

  • ruamel.yaml comment-preserving round-trip to replace all three hand-rolled config
    writers (bigger, riskier refactor).
  • Legacy slug aws-build vs builder reconcile (murky history, low value).
  • REVISION stamp for copy-installs, and uninstall order-sensitivity.

Tests

Full suite: 225 passed, 1 skipped. ruff clean (3 pre-existing N999 module-name
exclusions).

register_provider() rewrote config.yaml via load_config()/save_config() on
every plugin load — a full YAML round-trip that strips every comment in the
file, defeating the comment-preserving design of setup.sh/uninstall.sh.

Add change-detection: compare the rebuilt entry to the existing one and skip
save_config when they are equivalent and no legacy-slug migration happened.
Also align setup.sh's provider block with register_provider by emitting
discover_models: false (previously only register_provider set it).

Adds a regression test asserting a no-op load does not rewrite the config.
@github-actions github-actions Bot added the bug Something isn't working label Aug 22, 2026
iap added 2 commits August 22, 2026 11:11
register_provider() hardcoded a third copy of the model list as an
`or [...]` fallback when _declared_models() returned empty. That case is
already covered: _declared_models() -> backend.list_models() -> STATIC_MODELS,
so the only way to get [] is backend import failure, where advertising a
provider is pointless anyway.

Skip registration (return False, no save) when the catalog is empty instead
of duplicating the catalog. Adds regression test for the skip path.
yaml.dump sorts mapping keys alphabetically by default, reordering the
models: block away from the plugin.yaml declaration order. Pass
sort_keys=False so the generated provider block mirrors the source catalog.
Comment thread scripts/setup.sh Outdated
setup.sh hardcoded `model: "auto"` in the generated provider block, which
selects a model outside the advertised catalog when a custom plugin.yaml
declares a catalog without `auto`. Derive the default from models[0] — the
same value register_provider() uses at runtime — so the selected default is
always present in the provider's own models block.

Adds tests/test_setup.py exercising the real block-generation heredoc
(custom-catalog regression + shipped-default sanity check).
greptile-apps[bot]

This comment was marked as off-topic.

@iap
iap merged commit 4f53cb9 into main Aug 22, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant