Skip to content

fix(oas:sync): resolve path-item $refs so sync stops deleting pages - #47

Draft
cursor[bot] wants to merge 2 commits into
mainfrom
erunion/critical-bug-workflow-3318
Draft

fix(oas:sync): resolve path-item $refs so sync stops deleting pages#47
cursor[bot] wants to merge 2 commits into
mainfrom
erunion/critical-bug-workflow-3318

Conversation

@cursor

@cursor cursor Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor
🚦 Resolves
Data loss: oas:sync / lint --fix deleted valid reference pages when the spec used path-item $refs

🪄 Changes

extractOperations walked the raw OpenAPI document and only counted HTTP methods sitting directly on each path item. A path-level $ref — including the OAS 3.1 components.pathItems form — therefore contributed zero operations.

syncOneOas then treated every existing page for that spec as orphaned and deleted it. Same path for readme lint --fix (via the oas-reference validator).

Concrete trigger: a repo with reference/openapi.json like:

openapi: 3.1.0
paths:
  /pets:
    $ref: '#/components/pathItems/Pets'
components:
  pathItems:
    Pets:
      get:
        operationId: listPets

and a page reference/.../listPets.md with custom body. One readme oas:sync or readme lint --fix permanently deletes that page.

Fix:

  • Resolve internal JSON Pointer $refs (#/components/pathItems/…, #/paths/~1pets, operation-level pointers) before collecting operations.
  • Skip the delete pass when a $ref still cannot be inlined (e.g. a split spec pointing at ./paths/pets.yaml), so "we couldn't see the operation" is not treated as "the operation was removed".

No change to add/skip-on-collision behavior, path sanitization, or webhook handling (still covered by #43).

🧬 QA & Testing

  • Reproduced the deletion against an OAS 3.1 pathItems spec on unmodified main (page gone; changes.deleted = ['Pets/Other/listPets.md']).
  • After the fix, the same spec keeps the custom page, creates the missing sibling operation page, and the oas-reference validator no longer reports Operation not found.
  • Added regression tests in test/oas-sync.test.js (resolution, no-deletion for internal and external $refs) and test/oas-reference.test.js (no false missing-operation).
  • Full suite: 160/160 passing (npm test).
Open in Web View Automation 

extractOperations walked the raw spec and treated a path-level $ref
(OAS 3.1 components.pathItems, or a pointer to another path) as having
zero operations. oas:sync and lint --fix then deleted every matching
reference page.

Resolve internal JSON Pointer $refs before collecting operations, and
skip the delete pass when a $ref still cannot be inlined (e.g. a split
spec that points at ./paths/*.yaml).

Co-authored-by: Jon Ursenbach <erunion@users.noreply.github.com>
@erunion erunion added the bug Something isn't working label Aug 28, 2026 — with Cursor
@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown

Confidence Score: 3/5

This PR is not yet safe to merge because malformed internal path-item targets can still enable destructive page deletion and unresolved operation references can create bogus pages.

A non-object internal reference target discards sibling operations while leaving deletion enabled, and the previously reported unresolved-operation path still generates synthetic IDs that reach the add pass.

Files Needing Attention: src/commands/oas-sync.js

Reviews (2): Last reviewed commit: "fix(oas:sync): harden $ref resolution ag..." | Re-trigger Greptile

Comment thread src/commands/oas-sync.js Outdated
Comment thread src/commands/oas-sync.js Outdated
Comment thread src/commands/oas-sync.js Outdated
- Walk JSON Pointers with Object.hasOwn so #/__proto__ cannot
  escape into Object.prototype and empty the operation set.
- Treat a $ref as unresolved only after following the full chain,
  so a pointer that lands on an external, cyclic, or broken $ref
  still skips the delete pass.
- Overlay OAS 3.1 path-item siblings onto the resolved target
  instead of dropping them (local keys win).

Co-authored-by: Jon Ursenbach <erunion@users.noreply.github.com>
Comment thread src/commands/oas-sync.js
if (target == null) return obj;
const resolved = resolveRefObject(root, target, seen);
if (!hasSiblings) return resolved;
if (!resolved || typeof resolved !== 'object' || Array.isArray(resolved)) return resolved;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Non-object targets discard siblings

When a path item has a sibling HTTP operation beside an internal $ref whose target is an array or scalar, resolveRefObject returns only that target and discards the sibling. The reference is then treated as resolved, so extraction omits the sibling operation while deletion remains enabled, causing synchronization to delete its existing page and validation to report it as missing.

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