Skip to content

fix(oas:sync): recognize OAS 3.1 webhooks so sync stops deleting their pages - #43

Open
rossrdme wants to merge 3 commits into
fix/oas-sync-upload-parityfrom
fix/oas-sync-webhooks
Open

fix(oas:sync): recognize OAS 3.1 webhooks so sync stops deleting their pages#43
rossrdme wants to merge 3 commits into
fix/oas-sync-upload-parityfrom
fix/oas-sync-webhooks

Conversation

@rossrdme

Copy link
Copy Markdown
Contributor

Context

Found while validating #35's grouping fix against a repo synced by the real ReadMe platform. extractOperations only reads spec.paths, so an OAS 3.1 spec's top-level webhooks (calls the API itself makes to a client-registered URL — a same-shaped sibling of paths, not something a client calls into) are invisible to it.

That has two real, confirmed symptoms:

  • Data loss: oas:sync's delete pass treats any existing page whose operationId isn't in its known-operations set as orphaned and deletes it. A webhook-backed page's operationId is never in that set, so every sync run deletes it. Reproduced this against a real webhook page and confirmed it happens today on unmodified main.
  • False lint failure: the oas-reference validator reports Operation not found for the same, entirely valid pages.

Fix

extractOperations now also walks spec.webhooks, using the same synthetic <method>_<name> operationId scheme already used for paths operations without an explicit operationId. Generated pages are marked api.webhook: true, matching what the platform stamps on them. Grouping (by tag, or by the webhook's own name when untagged) and page generation fall out of the existing operationGroup/buildPageContent machinery from #35 — no special-casing needed.

Why this targets fix/oas-sync-upload-parity, not main

Untagged webhook grouping reuses #35's path-derived-group logic (a webhook's own name plays the same role a path does). Rebasing this onto main directly would mean duplicating that logic, or regressing untagged webhooks into the old shared Other/ bucket that #35 just fixed for paths. This is a stacked PR — review/merge after #35.

Verification

  • Reproduced the deletion bug against a real webhook page before the fix, confirmed it's gone after.
  • Added regression tests in test/oas-sync.test.js (page generation, api.webhook marker, no-deletion, tag/path collision) and test/oas-reference.test.js (no false "Operation not found").
  • Full suite: 103/103 passing.
  • End-to-end: wiped and regenerated all specs from scratch against a repo originally synced by the real platform (GitHub-sync commits, not CLI-generated) and diffed the result. Webhook pages, folders, filenames, and api.webhook markers now match exactly. Remaining diff is limited to already-known, out-of-scope gaps unrelated to this fix (category order with no declared position to anchor to, YAML quoting of brace-containing titles).

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

@rossrdme

Copy link
Copy Markdown
Contributor Author

@erunion this fixes a webhooks bug

@erunion

erunion commented Aug 27, 2026

Copy link
Copy Markdown
Member

@greptileai

@greptile-apps

greptile-apps Bot commented Aug 27, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Reviews (3): Last reviewed commit: "fix(oas:sync): follow chained pathItem $..." | Re-trigger Greptile

Comment thread src/commands/oas-sync.js
Comment thread src/commands/oas-sync.js
@erunion erunion added the bug Something isn't working label Aug 27, 2026
@rossrdme
rossrdme force-pushed the fix/oas-sync-webhooks branch from 83bb655 to 63af916 Compare August 28, 2026 04:20
Comment thread src/commands/oas-sync.js Outdated
…r pages

extractOperations only read spec.paths, so an OAS 3.1 spec's top-level
webhooks (calls the API itself makes to a client-registered URL — a separate,
same-shaped sibling of paths, not a path the client calls) were invisible to
it. Two symptoms, found while validating this against a repo synced by the
real platform:

- oas:sync's delete pass treats any existing page whose operationId isn't in
  its operation set as orphaned. A webhook-backed page's operationId is never
  in that set, so every sync run deleted it — reproduced against a real
  webhooks page and confirmed the deletion happens on unmodified main.
- The oas-reference lint validator reported a false "Operation not found" for
  the same pages.

extractOperations now also walks spec.webhooks, using the same synthetic
`<method>_<name>` operationId scheme already used for paths (verified it
reproduces the platform's own post_paymentcompleted / post_paymentfailed
convention exactly), and marks generated pages with `api.webhook: true` to
match what the platform stamps on them. Grouping (tag, or the webhook's own
name when untagged) and page generation fall out of the existing
operationGroup/buildPageContent machinery from #35 with no special-casing.

Stacked on fix/oas-sync-upload-parity (#35): untagged webhook grouping reuses
that branch's path-derived-group logic, so this targets that branch rather
than main.

Verified end-to-end against a repo synced by the real platform: wiping and
regenerating all specs from scratch now reproduces the platform's webhook
pages exactly (folder, filename, api.webhook, category title from the raw
webhook name) with zero remaining diff beyond already-known, out-of-scope
gaps (category ordering with no declared position, YAML quoting of
brace-containing titles).
…lve $ref webhooks

Two issues from Greptile review:

- Operation IDs collide: paths and webhooks are separate namespaces, but a
  synthetic <method>_<name> id can legitimately be identical across them
  (e.g. POST /orders and webhook POST orders both omitting operationId both
  synthesize to post_orders). extractOperations collected both into one Map
  keyed only by operationId, so the second (webhook) pass silently
  overwrote the path entry — sync then omitted the path's page entirely,
  and both oas-sync's delete pass and oas-reference's lint checks lost
  visibility of it. Added operationKey({operationId, isWebhook}) and use it
  everywhere an operation or an existing page is looked up by id — in
  oas-sync.js's pagesByOpId/specOps, and oas-reference.js's "Operation not
  found"/"Missing page" checks, which had the identical vulnerability on
  the read side (two on-disk pages sharing an operationId would collapse
  to one coveredOps entry). The written operationId itself is untouched —
  only the internal lookup key changed.

- Webhook references stay unresolved: an OAS 3.1 webhooks (or paths) entry
  can be a Reference Object (`{ $ref: '#/components/pathItems/Name' }`)
  rather than a literal Path Item. collect() iterated it directly, and
  since "$ref" isn't an HTTP method the whole entry was silently skipped.
  Added resolveLocalPathItemRef to resolve same-document
  #/components/pathItems/<name> refs before iterating methods (external
  refs and other pointer shapes are left unresolved, same graceful
  degradation as before). Applies uniformly to paths and webhooks since
  both go through the same collect() helper — this was a pre-existing gap
  for paths too, not unique to webhooks.

Verified both new regression tests fail against the prior code and pass
against this fix.
Addresses review from Greptile. resolveLocalPathItemRef only resolved one
level: a webhook/path entry whose pathItems target was itself a $ref (rather
than a literal Path Item) returned that intermediate Reference Object
unchanged. collect() then skipped it (still just a "$ref" key, no HTTP
methods), so sync deleted the existing page as orphaned and reference
validation reported it missing.

Now follows the chain until a literal Path Item is reached, tracking every
$ref string seen so a cycle returns the current (still-unresolved) node
instead of looping forever — same graceful degradation as an unresolvable
name. Added tests for a two-hop chain and a circular reference (confirms it
returns instead of hanging).
@rossrdme
rossrdme force-pushed the fix/oas-sync-webhooks branch from 63af916 to 109a29e Compare August 28, 2026 05:02
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.

2 participants