Skip to content

fix(downgrader): clear the 3.1 to 3.0 nullable-merge lists when conversion throws - #99

Open
dinwwwh wants to merge 1 commit into
mainfrom
claude/project-thread-fpuvkk
Open

dinwwwh wants to merge 1 commit into
mainfrom
claude/project-thread-fpuvkk

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 24, 2026

Copy link
Copy Markdown
Member

Requested by dinwwwh · project thread

Problem

downgradeV30 in src/v3.1-to-v3.0.ts keeps two module-level lists, twoBranchAnyOfs and anyOfTargets, for the nullable merge. They were cleared at the start of a call and at its successful end, but not when conversion threw (for example the RangeError on very deep nesting that the README lists). Until the next call, they kept the input and the partial output alive: on a 50k-schema doc, about 61.7 MB stayed retained after a throw versus 4.2 MB after a success.

Fix

Wrap the body in try/finally and clear both lists once, in finally. Since every call now ends with them empty, the clear at the start is dropped. The loop iterates twoBranchAnyOfs directly instead of splice(0), because mergeNullable never pushes to it.

Checks

  • pnpm test: 262 tests pass; pnpm run check clean.
  • Output byte-identical to main on 80 real 3.1 documents.
  • Checked by hand that a call after a deep-nesting RangeError still merges anyOf: [string, null] into nullable: true. No gc-based unit test, since that would be flaky.

Generated by Claude Code

…rsion throws

downgradeV30 keeps two module-level lists, twoBranchAnyOfs and anyOfTargets,
that were only cleared on success. A throw (such as the RangeError on very deep
nesting) left them holding the input and partial output until the next call.
Wrap the body in try/finally and clear them once there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NerLPPYvjVTGwnV3s1in2V
@dinwwwh dinwwwh self-assigned this Oct 24, 2026
@codecov

codecov Bot commented Oct 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Oct 24, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 21 untouched benchmarks


Comparing claude/project-thread-fpuvkk (167541a) with main (6a906e9)

Open in CodSpeed

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

  • Wrap downgradeV30 in try/finally — clears twoBranchAnyOfs and anyOfTargets even when conversion throws, so a failed call no longer retains the input and partial output until the next call.
  • Drop the start-of-call clear — now redundant since every call ends with both lists empty.
  • Iterate twoBranchAnyOfs directly instead of splice(0) — valid because mergeNullable never pushes to the list.

The direct iteration is safe: mergeNullable only calls isOnlyNullSchema, isRecord, getRef, find, rejectsNull, and Object.assign, none of which re-enter conversion. downgradeV30 is also not re-entrant — the chained 3.2→3.1→3.0 path calls the two downgraders sequentially — so removing the defensive start-of-call clear cannot leak state between calls.

I confirmed the fix empirically with a throwaway probe (200k two-branch-anyOf schemas plus a >1000-deep schema to force the documented RangeError): the base retains ~142 MB after the throw, while this branch retains ~0 MB. A successful call retains ~0 MB on both. pnpm test passes (262 tests).

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

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.

2 participants