fix(bridge): make isolated re-scan idempotent by seeding on function id - #390
Merged
Conversation
Isolated-registry seeding judged idempotency by registry-key presence, but reconciliation explodes a seeded method=None canonical into a method::path entry and deletes the original key (#358). A second scan therefore re-seeded the vanished key, resurrecting a stale route=None canonical that spec.py then documented as a phantom GET /api/<function-name> endpoint -- and, when an app and its Blueprint share one handler, tripped a duplicate operationId in a single generation. Seeding now short-circuits when any entry for the handler's _function_id already exists, so re-scans (and app+Blueprint combinations) stay idempotent. The global registry path was already idempotent and is unchanged. Adds regression tests for a same-app re-scan and a shared-handler app+Blueprint scan into one isolated registry.
This was referenced Aug 10, 2026
There was a problem hiding this comment.
Pull request overview
Fixes a regression in the app-scoped (isolated) registry scan path where re-scanning the same app/handler could re-seed a deleted canonical key and produce phantom endpoints + duplicate operationIds. The change makes isolated seeding idempotent by guarding on handler identity (_function_id) rather than key presence, and adds regression tests covering double-scan and shared-handler scenarios.
Changes:
- Update
_seed_canonical_entryto skip seeding when the isolated registry already contains any entry for the same_function_id. - Add isolated-mode regression tests to ensure re-scan idempotency and prevent phantom endpoints when the same handler is scanned twice (e.g., blueprint + app).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/azure_functions_openapi/bridge.py |
Adjusts isolated seeding logic to be identity-based and avoid re-seeding after reconcile rewrites keys. |
tests/test_spec_warnings.py |
Adds regression coverage for isolated re-scan idempotency and shared-handler scans, plus a helper for binding-only canonical shapes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+493
to
+496
| if reg.find_by_function_id(function_id) is not None: | ||
| return | ||
| for key, entry in seeds.items(): | ||
| if reg.get(key) is None: | ||
| reg.set(key, entry) | ||
| reg.set(key, entry) |
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
/api/handler_one) and duplicate operationIds._seed_canonical_entrynow guards on function identity (reg.find_by_function_id(function_id)) instead of key presence, so a canonical entry that was exploded + had its original key deleted during reconcile is not re-seeded on a subsequent scan.Root cause
The previous guard used
reg.get(key) is None. Reconcile explodes amethod=Nonecanonical intomethod::pathkeys and deletes the original key (#358). A second scan re-seeded the deleted key ->route=Nonephantom canonical ->spec.pyrenders it with function name + GET fallback. The global path was already idempotent; only isolation regressed.Changes
src/azure_functions_openapi/bridge.py: identity-guarded seeding in_seed_canonical_entry(sequential lock acquisition: snapshot under global lock, identity-guarded set under registry lock). Docstring updated.tests/test_spec_warnings.py: two regression tests underTestIsolatedRegistry(test_isolated_rescan_is_idempotent,test_isolated_shared_handler_scan_no_phantom) +_binding_only_apphelper. Verified both fail without the fix.Verification
make check-allgreen; coverage 96.23% (688 passed, 5 skipped).Closes #389