Skip to content

fix: scope AJV instances per lintDocument call to fix concurrent validation#2774

Open
vlindhol wants to merge 1 commit into
Redocly:mainfrom
vlindhol:fix/ajv-concurrent-lint-resolve
Open

fix: scope AJV instances per lintDocument call to fix concurrent validation#2774
vlindhol wants to merge 1 commit into
Redocly:mainfrom
vlindhol:fix/ajv-concurrent-lint-resolve

Conversation

@vlindhol
Copy link
Copy Markdown

@vlindhol vlindhol commented Apr 24, 2026

Summary

The global AJV singleton in packages/core/src/rules/ajv.ts captured a resolve closure from whichever document hit it first. When consumers like openapi-typescript run lintDocument over multiple APIs concurrently via Promise.all, the second document's resolve was silently discarded, producing spurious warnings:

⚠ Example validation errored: can't resolve reference #/components/schemas/Foo
  from id .../spec.yaml#/paths/~1items/post/requestBody/content/application~1json/schema.

The warnings only appear when:

  • Multiple APIs are linted concurrently
  • A same-doc $ref has an example/examples sibling
  • The spec contains a cross-file $ref (which is what triggers AJV's loadSchemaSync)

redocly lint itself wasn't affected because it processes APIs sequentially.

There was an earlier attempt at this fix in #2135 that went stale. This time I built a minimal repro that's checked in as a regression test.

Fix

Refactored rules/ajv.ts from free functions + global singleton into an AjvValidator class. Each of the three example-validation rules (no-invalid-media-type-examples, no-invalid-schema-examples, no-invalid-parameter-examples) now creates its own AjvValidator in its factory closure. Since initRules invokes each factory once per lintDocument call, every call gets a fresh validator — no shared state across documents.

The walker (walk.ts) stays untouched; AJV is purely a JSON-schema-validation concern living inside the rules that need it.

Check yourself

  • This PR follows the contributing guide
  • All new/updated code is covered by tests
  • Core code changed? - Tested with other Redocly products (internal contributions only)
  • New package installed? - Tested in different environments (browser/node)
  • Documentation update has been considered

Security

  • The security impact of the change has been considered
  • Code follows company security practices and guidelines

Note

Medium Risk
Refactors core example-validation plumbing from a global AJV singleton to per-lint validator instances, which could subtly affect schema caching/validation behavior across runs. Adds a concurrent lint regression test, lowering risk but still touching critical linting internals.

Overview
Fixes concurrent linting producing spurious "can’t resolve reference" example-validation warnings by removing the global AJV instance and introducing a per-run AjvValidator with dialect-specific cached instances.

The example-validation rules now create and pass a validator instance into validateExample, and lintDocument no longer force-resets AJV state. Adds a regression test plus OpenAPI fixtures that lint two specs concurrently via Promise.all, and updates AJV unit tests to target AjvValidator.

Reviewed by Cursor Bugbot for commit 8c72d29. Bugbot is set up for automated code reviews on this repo. Configure here.

@vlindhol vlindhol requested a review from a team as a code owner April 24, 2026 12:23
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 24, 2026

🦋 Changeset detected

Latest commit: 8c72d29

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@redocly/openapi-core Patch
@redocly/cli Patch
@redocly/respect-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vlindhol vlindhol force-pushed the fix/ajv-concurrent-lint-resolve branch from 75353fe to 4572e82 Compare April 24, 2026 12:45
@vlindhol vlindhol force-pushed the fix/ajv-concurrent-lint-resolve branch 2 times, most recently from a0f6003 to 18bfbff Compare May 4, 2026 09:11
@vlindhol vlindhol requested a review from a team as a code owner May 4, 2026 09:11
@vlindhol vlindhol force-pushed the fix/ajv-concurrent-lint-resolve branch from 18bfbff to 1157188 Compare May 4, 2026 09:13
Comment thread packages/core/src/walk.ts Outdated
specVersion: SpecVersion;
config?: Config;
getVisitorData: () => Record<string, unknown>;
ajvValidator: AjvValidator;
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.

Please, avoid adding ajvValidator in walker, because it is a separate concept of JSON schema validation. We use this validator only for specific rule and we use Ajv validators only there, meaning, that we don't need this in general pattern of walker. Additionally, that's why all tests in pipeline failed.

Copy link
Copy Markdown
Author

@vlindhol vlindhol May 19, 2026

Choose a reason for hiding this comment

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

Good point, moved AjvValidator out of the walker. Each of the three example-validation rules now creates its own AjvValidator instead.

@AlbinaBlazhko17
Copy link
Copy Markdown
Contributor

Thanks @vlindhol for the contribution! I left comment. Please, feel free to ask any questions.

@vlindhol vlindhol force-pushed the fix/ajv-concurrent-lint-resolve branch from 1157188 to a0b6228 Compare May 19, 2026 07:02
@vlindhol vlindhol requested a review from AlbinaBlazhko17 May 19, 2026 07:06
@vlindhol vlindhol force-pushed the fix/ajv-concurrent-lint-resolve branch from a0b6228 to 4a8455d Compare May 19, 2026 08:30
@DmitryAnansky
Copy link
Copy Markdown
Contributor

DmitryAnansky commented May 20, 2026

@vlindhol
Could you please update the PR description with the section required by the contribution guide?
Please make sure to review it as well.

## Check yourself

- [ ] This PR follows the [contributing guide](https://github.com/Redocly/redocly-cli/blob/main/CONTRIBUTING.md#pull-request-guidelines) <!-- TODO: make it required -->
- [ ] All new/updated code is covered by tests
- [ ] Core code changed? - Tested with other Redocly products (internal contributions only)
- [ ] New package installed? - Tested in different environments (browser/node)
- [ ] Documentation update has been considered <!-- TODO: make it required -->

## Security

- [ ] The security impact of the change has been considered <!-- required 🔒 -->
- [ ] Code follows company security practices and guidelines

@vlindhol
Copy link
Copy Markdown
Author

@vlindhol Could you please update the PR description with the section required by the contribution guide? Please make sure to review it as well.

Done! Was a bit unclear which boxes I need to check but I'm happy to be corrected!

…dation

The global AJV singleton in `ajv.ts` captured a `resolve` closure from
whichever document first created the instance. When consumers like
`openapi-typescript` process multiple APIs concurrently via
`Promise.all`, the second document's `resolve` was silently discarded,
producing spurious "Example validation errored: can't resolve reference"
warnings for valid same-document `$ref`s.

Replace the global singleton with an `AjvValidator` class instantiated
per `lintDocument` call and threaded through WalkContext/UserContext.
@vlindhol vlindhol force-pushed the fix/ajv-concurrent-lint-resolve branch from 4a8455d to 8c72d29 Compare May 20, 2026 14:50
@jeremyfiel
Copy link
Copy Markdown
Contributor

Does this have any impact on the single entry document concept? This appears to be a fix for code generators but the main purpose of this package is to lint/join/bundle descriptions. Please verify this does not introduce any other concept outside of using the entry document as the starting point for the functionality of this package.

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.

4 participants