Skip to content

fix(sdk): abort fromDevice pipe on MeshClient.disconnect#1312

Open
rinchen wants to merge 3 commits into
meshtastic:mainfrom
rinchen:fix/meshclient-fromdevice-abort-on-disconnect
Open

fix(sdk): abort fromDevice pipe on MeshClient.disconnect#1312
rinchen wants to merge 3 commits into
meshtastic:mainfrom
rinchen:fix/meshclient-fromdevice-abort-on-disconnect

Conversation

@rinchen

@rinchen rinchen commented Jul 19, 2026

Copy link
Copy Markdown

Summary

  • Start transport.fromDevice.pipeTo(decodePacket(this)) with an AbortController signal.
  • On disconnect(), abort the inbound pipe and await settlement before transport.disconnect().
  • Prevents leaving the transport readable locked after disconnect (serial "port is already open" / BLE reconnect failures).

Context

Colorado-Mesh/mesh-client carries an equivalent fix as a pnpm patch against @jsr/meshtastic__core@2.6.6 (MeshDevice). The SDK has since moved to MeshClient in this monorepo, but the unaborted pipeTo pattern remains.

packages/transport-web-serial already creates a per-instance toDeviceStream and aborts cleanly; this PR aligns MeshClient inbound teardown with that pattern.

Test plan

  • Connect via Web Serial, disconnect, reconnect without "port is already open"
  • Connect via Web Bluetooth (if available), disconnect, reconnect without a stuck GATT session
  • Confirm inbound packets still decode while connected

Summary by CodeRabbit

  • Bug Fixes
    • Improved connection shutdown reliability by ensuring inbound device data decoding is fully stopped during disconnect.
    • Added controlled cancellation to prevent expected abort/termination errors from surfacing as unhandled failures.
    • Ensured disconnect waits for the inbound decoding pipeline to complete (while safely ignoring expected cancellation outcomes).

pipeTo(decodePacket) was started without an AbortSignal, so disconnect
closed toDevice/transport while the inbound pipe kept the readable locked.
That left serial/BLE ports busy and caused "port is already open" on
reconnect.

Abort the fromDevice pipe and await settlement before transport.disconnect().

Carried as a pnpm patch in Colorado-Mesh/mesh-client against @meshtastic/core.
@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown

@rinchen is attempting to deploy a commit to the Meshtastic Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 868b62cf-b03a-4182-808c-1da335537fc1

📥 Commits

Reviewing files that changed from the base of the PR and between 3e1e062 and b1a633e.

📒 Files selected for processing (1)
  • packages/sdk/src/core/client/MeshClient.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/sdk/src/core/client/MeshClient.ts

📝 Walkthrough

Walkthrough

MeshClient now manages the lifecycle of its background device-decoding stream, cancelling and awaiting it during disconnect before closing the transport.

Changes

Device decoding lifecycle

Layer / File(s) Summary
Abortable decoding and disconnect sequencing
packages/sdk/src/core/client/MeshClient.ts
Stores the decoding stream’s abort controller and termination promise, starts the stream with cancellation support, suppresses expected cancellation errors, and completes the pipe before transport shutdown.

Estimated code review effort: 2 (Simple) | ~10 minutes

Poem

I’m a rabbit with a tidy stream,
I hop through packets like a dream.
Abort, await, then close the door—
No stray rejection on the floor!
Squeak, the shutdown’s safe once more.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: aborting the inbound fromDevice pipe during MeshClient disconnect.
Description check ✅ Passed The description covers the summary, context, and test plan, and is mostly complete despite missing some optional template sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/sdk/src/core/client/MeshClient.ts (1)

151-152: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Log unexpected errors from the decoding pipe.

Silently swallowing all errors prevents unhandled promise rejections for expected AbortErrors, but it also hides underlying transport or stream errors that might occur during normal operation.

Consider logging non-abort errors to aid in debugging:

♻️ Proposed refactor
-    // Swallow abort/cancel rejection so an unhandled rejection does not surface.
-    void this._fromDevicePipe.catch(() => {});
+    // Swallow abort/cancel rejection so an unhandled rejection does not surface.
+    void this._fromDevicePipe.catch((err) => {
+      if (err instanceof Error && err.name !== "AbortError") {
+        this.log.error(Emitter[Emitter.ConnectionStatus], "Device decoding pipe failed", err);
+      }
+    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sdk/src/core/client/MeshClient.ts` around lines 151 - 152, Update
the _fromDevicePipe rejection handler in MeshClient to ignore only expected
abort/cancel errors and log all other decoding, transport, or stream errors.
Preserve the existing prevention of unhandled rejections while ensuring
unexpected errors include their details in the client’s established logging
mechanism.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/sdk/src/core/client/MeshClient.ts`:
- Around line 358-366: Update disconnect() so this._fromDeviceAc.abort() and
awaiting this._fromDevicePipe occur before awaiting
this.transport.toDevice.close(). Wrap the writable close in a try/catch to
tolerate expected physically disconnected transport errors, then ensure
this.transport.disconnect() still executes so teardown completes.

---

Nitpick comments:
In `@packages/sdk/src/core/client/MeshClient.ts`:
- Around line 151-152: Update the _fromDevicePipe rejection handler in
MeshClient to ignore only expected abort/cancel errors and log all other
decoding, transport, or stream errors. Preserve the existing prevention of
unhandled rejections while ensuring unexpected errors include their details in
the client’s established logging mechanism.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2e04c58c-3393-44ce-b84e-291d961c6e48

📥 Commits

Reviewing files that changed from the base of the PR and between 948474a and 60a7af6.

📒 Files selected for processing (1)
  • packages/sdk/src/core/client/MeshClient.ts

Comment thread packages/sdk/src/core/client/MeshClient.ts Outdated
Review feedback: toDevice.close() can reject on a physically disconnected
transport, which previously skipped the abort and left the pipe locked.
Abort first, tolerate writable close errors, and log non-abort decoding
pipe failures instead of swallowing everything.
DOMException abort rejections are not instanceof Error in all runtimes,
so check err?.name directly and stringify non-Error reasons. Include the
caught error detail when toDevice.close() fails during disconnect.
rinchen added a commit to Colorado-Mesh/mesh-client that referenced this pull request Jul 19, 2026
…down (#693)

* chore: track upstream patch PRs and harden Meshtastic disconnect teardown

- Add patches/README.md with upstream PR links (or intentionally-local rationale)
  for every pnpm.patchedDependencies overlay.
- Record ratspeak/rsReticulum#11 and ratspeak/rsLXMF#4 in the Ratspeak overlay
  README and update.sh RATSPEAK_PATCH_ENTRIES so pnpm run update can warn on merge.
- Align @jsr/meshtastic__core patch with meshtastic/web#1312 review feedback:
  abort the fromDevice pipe before awaiting toDevice.close, tolerate writable
  close errors on abrupt disconnect, and log non-AbortError decoding pipe failures.

* fix: address CodeRabbit review on Meshtastic disconnect patch

- Drop the instanceof Error guard in the inbound pipe catch so DOMException
  abort/stream rejections are classified by name, and stringify non-Error
  reasons for the log.
- Include the caught error detail in the toDevice.close() debug log.
- Document why disconnect yields three microtask ticks before releasing
  the transport.
@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
web-test Ready Ready Preview, Comment Jul 20, 2026 2:12am

Request Review

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.

1 participant