Skip to content

fix: better error handling in connections#3182

Open
mvadari wants to merge 13 commits intomainfrom
connection-parse-error
Open

fix: better error handling in connections#3182
mvadari wants to merge 13 commits intomainfrom
connection-parse-error

Conversation

@mvadari
Copy link
Copy Markdown
Collaborator

@mvadari mvadari commented Jan 12, 2026

High Level Overview of Change

This PR does better error handling for certain edge-case errors (such as jsonInvalid) in responses from rippled.

Context of Change

I was working on some tests for rippled (XRPLF/rippled#6206) and discovered that the xrpl.js client hangs when some errors are received.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Did you update HISTORY.md?

  • Yes

Test Plan

I wasn't able to write a good integration test for some reason but I did write a unit test and test by hand against a standalone node.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Client error handling updated to parse and route nested JSON error payloads (including jsonInvalid), adjust emission for null-type responses, and add tests plus mock changes to exercise the new error paths. HISTORY updated to document the fix.

Changes

Cohort / File(s) Summary
Changelog
packages/xrpl/HISTORY.md
Added Unreleased entry documenting improved Client error handling for edge-case error responses; noted export line in 4.6.0.
Client connection
packages/xrpl/src/client/connection.ts
Added ESLint directive; when data.type is null emit data.error_message ?? data.error; handle data.type === 'error' with non-null data.value by parsing value and calling requestManager.reject(parsedValue.id, new Error(data.error)).
Tests
packages/xrpl/test/connection.test.ts
Added test "should handle jsonInvalid errors" using AccountInfoRequest; asserts error produced for jsonInvalid responses.
Test mocks
packages/xrpl/test/createMockRippled.ts
When mock response type: 'error' and value exists, parse value, inject request.id into value.id, re-serialize, and return that payload so client maps/rejects the correct request.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

bug

Suggested reviewers

  • ckeshava
  • pdp2121
  • Patel-Raj11

Poem

🐇 I nibbled through a nested string,
found an id and set it free to spring.
Errors now hop home to their nest,
tests applaud — a tidy quest. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: better error handling in connections' clearly and concisely summarizes the main change: improving error handling for edge cases in the client's connection handling.
Description check ✅ Passed The PR description covers all key required sections: high-level overview, context of change with reference to the upstream issue, type of change (bug fix), HISTORY.md update confirmation, and test plan details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch connection-parse-error

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c8c667 and 52e8e67.

📒 Files selected for processing (4)
  • packages/xrpl/HISTORY.md
  • packages/xrpl/src/client/connection.ts
  • packages/xrpl/test/connection.test.ts
  • packages/xrpl/test/createMockRippled.ts
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: ckeshava
Repo: XRPLF/xrpl.js PR: 2874
File: packages/xrpl/test/integration/transactions/permissionedDomain.test.ts:25-80
Timestamp: 2025-01-08T02:12:28.489Z
Learning: The rippled C++ implementation (PR #5161) includes comprehensive test coverage for PermissionedDomain (XLS-80d) error cases. The JS SDK tests focus on the happy path since the error cases are already validated at the rippled level, following the principle of not duplicating complex validation testing across SDK implementations.
Learnt from: shawnxie999
Repo: XRPLF/xrpl.js PR: 2661
File: packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts:29-118
Timestamp: 2024-12-06T19:25:15.376Z
Learning: In the XRPLF/xrpl.js TypeScript client library, when writing tests (e.g., in `packages/xrpl/test/integration/transactions/`), we generally do not need to test rippled server behaviors, because those behaviors are covered by rippled's own integration and unit tests.
📚 Learning: 2024-12-06T19:25:15.376Z
Learnt from: shawnxie999
Repo: XRPLF/xrpl.js PR: 2661
File: packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts:29-118
Timestamp: 2024-12-06T19:25:15.376Z
Learning: In the XRPLF/xrpl.js TypeScript client library, when writing tests (e.g., in `packages/xrpl/test/integration/transactions/`), we generally do not need to test rippled server behaviors, because those behaviors are covered by rippled's own integration and unit tests.

Applied to files:

  • packages/xrpl/test/createMockRippled.ts
  • packages/xrpl/src/client/connection.ts
  • packages/xrpl/test/connection.test.ts
📚 Learning: 2024-12-06T19:27:11.147Z
Learnt from: shawnxie999
Repo: XRPLF/xrpl.js PR: 2661
File: packages/xrpl/test/integration/transactions/clawback.test.ts:165-178
Timestamp: 2024-12-06T19:27:11.147Z
Learning: In the integration tests for `clawback.test.ts`, it's acceptable to use `ts-expect-error` to bypass type checking when verifying ledger entries, and no additional type safety improvements are needed.

Applied to files:

  • packages/xrpl/test/createMockRippled.ts
  • packages/xrpl/test/connection.test.ts
📚 Learning: 2024-12-06T18:44:55.095Z
Learnt from: shawnxie999
Repo: XRPLF/xrpl.js PR: 2661
File: packages/xrpl/test/models/MPTokenAuthorize.test.ts:60-71
Timestamp: 2024-12-06T18:44:55.095Z
Learning: In the XRPL.js library's TypeScript test file `packages/xrpl/test/models/MPTokenAuthorize.test.ts`, negative test cases for invalid `Account` address format, invalid `Holder` address format, invalid `MPTokenIssuanceID` format, and invalid flag combinations are not necessary.

Applied to files:

  • packages/xrpl/test/connection.test.ts
📚 Learning: 2025-01-08T02:12:28.489Z
Learnt from: ckeshava
Repo: XRPLF/xrpl.js PR: 2874
File: packages/xrpl/test/integration/transactions/permissionedDomain.test.ts:25-80
Timestamp: 2025-01-08T02:12:28.489Z
Learning: The rippled C++ implementation (PR #5161) includes comprehensive test coverage for PermissionedDomain (XLS-80d) error cases. The JS SDK tests focus on the happy path since the error cases are already validated at the rippled level, following the principle of not duplicating complex validation testing across SDK implementations.

Applied to files:

  • packages/xrpl/test/connection.test.ts
📚 Learning: 2025-02-12T23:28:55.377Z
Learnt from: mvadari
Repo: XRPLF/xrpl.js PR: 2895
File: packages/xrpl/test/models/clawback.test.ts:0-0
Timestamp: 2025-02-12T23:28:55.377Z
Learning: The `validate` function in xrpl.js is synchronous and should be tested using `assert.doesNotThrow` rather than async assertions.

Applied to files:

  • packages/xrpl/test/connection.test.ts
🧬 Code graph analysis (2)
packages/xrpl/test/createMockRippled.ts (2)
packages/xrpl/src/client/connection.ts (1)
  • request (291-319)
packages/xrpl/src/client/index.ts (1)
  • request (340-359)
packages/xrpl/test/connection.test.ts (3)
packages/xrpl/src/client/connection.ts (1)
  • request (291-319)
packages/xrpl/src/client/index.ts (1)
  • request (340-359)
packages/xrpl/src/models/methods/index.ts (1)
  • AccountInfoRequest (523-523)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (4)
packages/xrpl/HISTORY.md (1)

7-9: LGTM!

The changelog entry accurately documents the error handling improvement and follows the existing format.

packages/xrpl/test/createMockRippled.ts (1)

24-29: LGTM!

The logic correctly injects the request ID into the error value payload to support the new error handling path in connection.ts. Since this is test utility code with controlled inputs, the implementation is appropriate.

packages/xrpl/src/client/connection.ts (1)

354-354: LGTM!

Good improvement to provide a fallback when error_message is missing, ensuring errors like slowDown display a meaningful message even when error_message is not set.

packages/xrpl/test/connection.test.ts (1)

1033-1050: LGTM!

Good test coverage for the new jsonInvalid error handling path. The test properly validates that the error is correctly propagated with the expected name and message.

Comment on lines +372 to +386
if (data.type === 'error' && data.value != null) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed
const parsedValue: Record<string, unknown> = JSON.parse(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
data.value as string,
)
if (parsedValue.id != null) {
this.requestManager.reject(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
parsedValue.id as string | number,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
new Error(data.error as string),
)
}
}
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.

⚠️ Potential issue | 🟠 Major

Wrap JSON.parse in a try-catch to handle malformed data.value.

If data.value contains invalid JSON, JSON.parse will throw an uncaught exception. While rippled should always send valid JSON, defensive error handling would prevent the client from crashing on unexpected malformed responses.

🛡️ Proposed fix
     if (data.type === 'error' && data.value != null) {
-      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed
-      const parsedValue: Record<string, unknown> = JSON.parse(
-        // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
-        data.value as string,
-      )
-      if (parsedValue.id != null) {
-        this.requestManager.reject(
-          // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
-          parsedValue.id as string | number,
-          // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
-          new Error(data.error as string),
-        )
+      try {
+        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed
+        const parsedValue: Record<string, unknown> = JSON.parse(
+          // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
+          data.value as string,
+        )
+        if (parsedValue.id != null) {
+          this.requestManager.reject(
+            // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
+            parsedValue.id as string | number,
+            // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
+            new Error(data.error as string),
+          )
+        }
+      } catch (error) {
+        if (error instanceof Error) {
+          this.emit('error', 'badMessage', error.message, data)
+        }
       }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (data.type === 'error' && data.value != null) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed
const parsedValue: Record<string, unknown> = JSON.parse(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
data.value as string,
)
if (parsedValue.id != null) {
this.requestManager.reject(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
parsedValue.id as string | number,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
new Error(data.error as string),
)
}
}
if (data.type === 'error' && data.value != null) {
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed
const parsedValue: Record<string, unknown> = JSON.parse(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
data.value as string,
)
if (parsedValue.id != null) {
this.requestManager.reject(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
parsedValue.id as string | number,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
new Error(data.error as string),
)
}
} catch (error) {
if (error instanceof Error) {
this.emit('error', 'badMessage', error.message, data)
}
}
}

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/xrpl/HISTORY.md (1)

5-15: ⚠️ Potential issue | 🟡 Minor

Changelog entry belongs under ## Unreleased, not ## 4.6.0.

The new ### Fixed block was added inside the already-released 4.6.0 section (tagged 2026-02-12), while the ## Unreleased section directly above it is empty. Because this PR is still open, any code it introduces will ship in a future release, not 4.6.0. Misplacing it here will cause the entry to be missed or duplicated when the next version is cut.

📝 Proposed fix
 ## Unreleased
 
+### Fixed
+* Better error handling in the `Client` for edge case errors
+
 ## 4.6.0 (2026-02-12)
 
 ### Added
 * Add `faucetProtocol` (http or https) option to `fundWallet` method. Makes `fundWallet` work with locally running faucet servers.
 * Add `signLoanSetByCounterparty` and `combineLoanSetCounterpartySigners` helper functions to sign and combine LoanSet transactions signed by the counterparty.
 * Add newly added fields to `Loan`, `LoanBroker` and `Vault` ledger objects and lending protocol related transaction types.
 
-### Fixed
-* Better error handling in the `Client` for edge case errors
-
 ## 4.5.0 (2025-12-16)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/xrpl/HISTORY.md` around lines 5 - 15, Move the misplaced changelog
entry "Better error handling in the `Client` for edge case errors" from the
released section `## 4.6.0 (2026-02-12)` into the `## Unreleased` section:
locate the `### Fixed` block currently under `## 4.6.0 (2026-02-12)` in
HISTORY.md and cut that bullet (or the entire `### Fixed` subsection if it's
only the one entry) and paste it under the `## Unreleased` header, preserving
the `### Fixed` heading if needed so the entry appears in the next release
notes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/xrpl/HISTORY.md`:
- Around line 5-15: Move the misplaced changelog entry "Better error handling in
the `Client` for edge case errors" from the released section `## 4.6.0
(2026-02-12)` into the `## Unreleased` section: locate the `### Fixed` block
currently under `## 4.6.0 (2026-02-12)` in HISTORY.md and cut that bullet (or
the entire `### Fixed` subsection if it's only the one entry) and paste it under
the `## Unreleased` header, preserving the `### Fixed` heading if needed so the
entry appears in the next release notes.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b39c219 and a89a7ad.

📒 Files selected for processing (1)
  • packages/xrpl/HISTORY.md

pdp2121
pdp2121 previously approved these changes Mar 3, 2026
pdp2121
pdp2121 previously approved these changes Apr 29, 2026
Copilot AI review requested due to automatic review settings April 29, 2026 18:59
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Improves how xrpl.js handles certain edge-case rippled error frames (e.g., jsonInvalid) so requests don’t appear to hang waiting for a standard type: "response" envelope.

Changes:

  • Add special handling for type: "error" frames containing a JSON-encoded value payload to reject the pending request.
  • Update the mock rippled server to inject request ids into error.value payloads for tests.
  • Add a unit test covering jsonInvalid behavior and update HISTORY.md.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
packages/xrpl/src/client/connection.ts Adds type: "error" + value parsing to reject pending requests; tweaks emitted error message fallback.
packages/xrpl/test/createMockRippled.ts Adjusts mock response creation to place id inside error.value JSON payload.
packages/xrpl/test/connection.test.ts Adds a unit test asserting jsonInvalid errors reject instead of hanging.
packages/xrpl/HISTORY.md Adds an unreleased changelog entry; also modifies a 4.5.0 bullet.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +373 to +377
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed
const parsedValue: Record<string, unknown> = JSON.parse(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
data.value as string,
)
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

data.value is JSON.parsed without a try/catch or even checking that it's a string. If rippled ever sends a non-JSON string (or an object) in value, this will throw and can crash the WS message handler. Please guard with a type check and wrap the parse in error handling that emits a structured 'badMessage' error (similar to the existing top-level JSON.parse handling).

Suggested change
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed
const parsedValue: Record<string, unknown> = JSON.parse(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
data.value as string,
)
if (typeof data.value !== 'string') {
this.emit(
'error',
'badMessage',
'Expected error value to be a JSON string',
data,
)
return
}
let parsedValue: Record<string, unknown>
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed
parsedValue = JSON.parse(data.value) as Record<string, unknown>
} catch (error) {
if (error instanceof Error) {
this.emit('error', 'badMessage', error.message, data.value)
} else {
this.emit('error', 'badMessage', error, error)
}
return
}

Copilot uses AI. Check for mistakes.
Comment on lines +379 to +384
this.requestManager.reject(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
parsedValue.id as string | number,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
new Error(data.error as string),
)
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

this.requestManager.reject(...) can throw (it throws XrplError when there is no pending promise with that id). In this new data.type === 'error' path that exception is currently unhandled, so an unexpected/late error frame could take down the message handler. Wrap the reject call in a try/catch and surface failures via this.emit('error', 'badMessage', ...) or similar.

Suggested change
this.requestManager.reject(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
parsedValue.id as string | number,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
new Error(data.error as string),
)
try {
this.requestManager.reject(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
parsedValue.id as string | number,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
new Error(data.error as string),
)
} catch (error) {
if (error instanceof Error) {
this.emit('error', 'badMessage', error.message, message)
} else {
this.emit('error', 'badMessage', error, error)
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +379 to +384
this.requestManager.reject(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
parsedValue.id as string | number,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
new Error(data.error as string),
)
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

This code rejects the pending request with new Error(...), but the codebase documents that errors thrown by xrpl.js should be XrplErrors (and request failures typically reject with RippledError). Using the built-in Error here changes the error type contract and drops useful context. Consider rejecting with new RippledError(data.error_message ?? data.error, data) (or another XrplError subtype) so callers get consistent error types and access to the original frame.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +28
const value = JSON.parse(response.value as string)
value.id = request.id
response.value = JSON.stringify(value)
return JSON.stringify(response)
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

createResponse mutates the provided response object (response.value = ...). Since mocked responses are stored and can be reused across multiple requests, this can leak an earlier request’s id into later responses. Also, JSON.parse(response.value as string) is unguarded and will throw if value isn’t valid JSON. Prefer creating a new response object (no mutation) and handle/propagate parse failures with a clearer test error.

Suggested change
const value = JSON.parse(response.value as string)
value.id = request.id
response.value = JSON.stringify(value)
return JSON.stringify(response)
if (typeof response.value !== 'string') {
throw new XrplError(
`Bad error response format. \`value\` must be a JSON string. ${JSON.stringify(
response,
)}`,
)
}
let value: Record<string, unknown>
try {
value = JSON.parse(response.value) as Record<string, unknown>
} catch (error) {
throw new XrplError(
`Bad error response format. \`value\` must be valid JSON. ${JSON.stringify(
response,
)}`,
)
}
return JSON.stringify({
...response,
value: JSON.stringify({ ...value, id: request.id }),
})

Copilot uses AI. Check for mistakes.
Comment on lines +1066 to +1069
await clientContext.client.request(request).catch((error) => {
assert.strictEqual(error.name, 'Error')
assert.strictEqual(error.message, 'jsonInvalid')
})
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

This test will silently pass if the request unexpectedly resolves (because the assertions are only in .catch). It should explicitly fail on success (e.g., await assertRejects(...) or try { await ...; assert.fail(...) } catch (e) { ... }). Also, once the production code returns an XrplError/RippledError for this case, the assertions should verify that type instead of the built-in 'Error' name.

Suggested change
await clientContext.client.request(request).catch((error) => {
assert.strictEqual(error.name, 'Error')
assert.strictEqual(error.message, 'jsonInvalid')
})
await assertRejects(
clientContext.client.request(request),
Error,
'jsonInvalid',
)

Copilot uses AI. Check for mistakes.
Comment thread packages/xrpl/HISTORY.md
Comment on lines +30 to +31
* Export signing and binary co
* dec utilities.
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

The release note bullet under 4.5.0 was accidentally split and truncated ("binary co" / "dec utilities"). Please restore it to a single bullet so the changelog renders correctly.

Suggested change
* Export signing and binary co
* dec utilities.
* Export signing and binary codec utilities.

Copilot uses AI. Check for mistakes.
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.

3 participants