Common Web Serial and @gurezo/web-serial-rxjs problems, with check steps and fixes. Start with Quick Start requirements if you have not connected yet. For error code tables, see API concepts and design notes. For recommended next actions after an error, see the Error Recovery Matrix.
Symptoms: Clicking Connect does nothing, or the browser dialog opens but your device is not listed.
Check:
connect$() from a user gesture (button click). The browser blocks the picker otherwise — see Quick Start – Requirements.Fix: Wire Connect to a click handler, fix secure context / browser support, free the port, then call connect$() again and subscribe.
Symptoms: state$ stays at unsupported, or connect$ fails with SerialErrorCode.BROWSER_NOT_SUPPORTED.
Check:
import { isWebSerialSupported } from '@gurezo/web-serial-rxjs';
if (!isWebSerialSupported()) {
console.error('Web Serial API is not available in this browser');
}
Fix: Use an officially supported desktop browser with Web Serial (Chrome 89+, Edge 89+, Opera 75+, Firefox 151+). Safari does not currently implement the Web Serial API. Mobile browsers are untested and out of official support — many also lack the API (so isWebSerialSupported() is false). See Browser support and support policy, the Examples requirements, and the repository README.
Symptoms: navigator.serial is missing, or Examples show an insecure-context message.
Check: The origin must be HTTPS or http://localhost / http://127.0.0.1. Plain http:// on a LAN IP or hostname will not expose Web Serial.
Fix: Serve over HTTPS, use localhost for local development, or tunnel with a tool that terminates TLS. Details: Quick Start – Requirements.
Symptoms: Nothing happens after calling connect$(), send$(), disconnect$(), or dispose$() — no dialog, no send, no teardown.
Check: These methods return cold Observables. They only run when you subscribe.
// Does nothing until subscribed
session.connect$();
// Runs the connection flow
session.connect$().subscribe({
error: (e) => console.error(e),
});
Fix: Always subscribe (or use an operator that subscribes, such as converting to a Promise carefully). The same rule applies to send$, disconnect$, and dispose$. See Quick Start – Running imperative methods (cold Observables).
Symptoms: Sends appear ignored, replies never arrive on lines$, or the terminal looks broken.
Check:
\r\n on send. Prefer session.send$(${line}\r\n) or a small sendLine helper — see Advanced Usage – Send line.lines$ for newline-delimited logs/parsers. Prefer receive$ (or terminalText$) for terminal-style \r redraw.Fix: Align send endings with the device, and pick lines$ vs receive$ for the consumer. Recipes: Advanced Usage – Line framing.
Symptoms: Picker shows the device but open fails, or PORT_OPEN_FAILED / related errors appear on errors$.
Check: Another process or browser tab may hold the port. Only one opener can own a serial port at a time on most OSes.
Fix: Close other serial tools and tabs, unplug/replug if needed, then connect again from a user gesture. Inspect errors$ — see Inspecting SerialError.
Symptoms: After disconnect or an error, Connect does nothing useful, or you get SESSION_DISPOSED / PORT_ALREADY_OPEN.
Check:
disconnect$, the same session is reusable from 'idle' (or recovered from 'error'). Subscribe to disconnect$, wait for idle on state$, then connect$() again.dispose$, the session is permanent. Create a new createSerialSession() — do not reuse the disposed instance.connect$ while already 'connecting' or 'connected' (PORT_ALREADY_OPEN).Fix: Drive UI from state$. For baud-rate changes or full teardown, dispose$ then create a new session. See Quick Start – Disconnect / Dispose, Framework session lifecycle, and Advanced Usage – recovery.
Symptoms: Failures are hard to classify from Error.message alone.
Check: Subscribe to errors$ and narrow with error.is(SerialErrorCode.*). For cause-bearing codes, read error.context.cause.
import { SerialErrorCode } from '@gurezo/web-serial-rxjs';
session.errors$.subscribe((error) => {
if (error.is(SerialErrorCode.OPERATION_CANCELLED)) {
console.info('User cancelled the port picker');
return;
}
if (error.is(SerialErrorCode.READ_FAILED)) {
console.error('Read failed:', error.context.cause);
return;
}
console.error(error.code, error.message, error.context);
});
Also handle error on connect$().subscribe({ error }) and send$().subscribe({ error }) — the same SerialError is multiplexed on errors$.
Fix: Branch on codes; full tables live in SerialError / SerialErrorCode. Use the Error Recovery Matrix below to decide the next action.
Parent: #585 · Issue: #594 · Related: SerialError / SerialErrorCode · Timeout, cancel, and retry · Advanced Usage – Reconnect on fatal error · Framework session lifecycle
errors$ (and subscribe({ error }) on cold methods) tells you what failed. This matrix tells you what to do next: stay connected, reconnect the same session, or dispose and create a new one.
The library does not auto-reconnect. App-side retry policies belong in your RxJS composition — see Timeout, cancel, and retry.
error.is(SerialErrorCode.*) (or catch factory throws for INVALID_*).state$ to 'error' and tear down the port / read pump; non-fatal errors leave the session connected.connect$ on a disposed instance.flowchart TD
err["errors$ or subscribe error"]
check{"error.is(code)"}
fatal["fatal: state is error"]
nonFatal["non-fatal: stay connected"]
thrown["factory throw"]
reconnect["disconnect$ then connect$"]
disposeNew["dispose$ then new session"]
appFix["fix options or UI state"]
browser["change browser"]
err --> check
check -->|"PORT_OPEN_FAILED / CONNECTION_LOST / READ_FAILED"| fatal
check -->|"WRITE_FAILED / LINE_BUFFER_OVERFLOW / PORT_*"| nonFatal
check -->|"INVALID_*"| thrown
check -->|BROWSER_NOT_SUPPORTED| browser
check -->|SESSION_DISPOSED| disposeNew
fatal --> reconnect
nonFatal --> appFix
thrown --> appFix
| Error | Severity | Recoverable | Recommended action | Reconnect (same session) | Dispose + new session |
|---|---|---|---|---|---|
BROWSER_NOT_SUPPORTED |
non-fatal | no | Switch to a supported Chromium / Firefox desktop browser. | no | optional (UI teardown) |
PORT_OPEN_FAILED |
fatal | yes | Free the port (other apps/tabs), check cable / permissions, then connect again. | yes | only if you abandon this session |
PORT_ALREADY_OPEN |
non-fatal | yes | Wait for 'idle' / 'error', or disconnect$ first, then connect$. |
after disconnect | no |
PORT_NOT_OPEN |
non-fatal | yes | Call connect$ before send$ / disconnect$. |
n/a (connect first) | no |
READ_FAILED |
fatal | yes | Check cable / device / drivers, then reconnect. | yes | if reconnect keeps failing |
WRITE_FAILED |
non-fatal | yes | Inspect state$ and context.cause; resend if still 'connected'. |
only if connection also drops | no |
CONNECTION_LOST |
fatal | yes | Check cable / device, then reconnect. | yes | if you change baud rate or abandon session |
OPERATION_CANCELLED |
fatal | yes (manual) | User closed the picker — optional: offer Connect again. Do not auto-retry. | yes (user gesture) | no |
LINE_BUFFER_OVERFLOW |
non-fatal | yes | Raise lineBuffer.maxChars, ensure device line endings, or parse on receive$. Session stays connected. |
no (not needed) | no |
SESSION_DISPOSED |
fatal | no | Instance is dead — create a new createSerialSession(). |
no | already disposed; create new |
UNKNOWN |
fatal | maybe | Inspect context.cause. Prefer reconnect; if unclear, dispose and create a new session. |
try first | if cause is unclear |
INVALID_FILTER_OPTIONS |
throw (factory) | yes | Fix filters and call createSerialSession() again. |
n/a | recreate with fixed options |
INVALID_TERMINAL_BUFFER_OPTIONS |
throw (factory) | yes | Fix terminalBuffer and recreate the session. |
n/a | recreate with fixed options |
INVALID_LINE_BUFFER_OPTIONS |
throw (factory) | yes | Fix lineBuffer and recreate the session. |
n/a | recreate with fixed options |
INVALID_CONNECTION_OPTIONS |
throw (factory) | yes | Fix connection options (e.g. baudRate) and recreate the session. |
n/a | recreate with fixed options |
PORT_NOT_AVAILABLE |
reserved | n/a | Not emitted in v4. Handle acquisition failures as PORT_OPEN_FAILED / OPERATION_CANCELLED. |
— | — |
OPERATION_TIMEOUT |
reserved | n/a | Not emitted in v4. No core timeout API yet; compose timeouts in the app. | — | — |
fatal: session reports via reportError, moves toward 'error', and tears down the live port / read pump. Recover with disconnect$ (if needed) then connect$ on the same instance unless Dispose says otherwise.non-fatal: multiplexed on errors$ only; connection continues unless you choose to disconnect.throw (factory): raised synchronously from createSerialSession() — not delivered on errors$.reserved: still present on the SerialErrorCode object but unreachable at runtime in v4.dispose$, never reuse the instance — always create a new session (Framework session lifecycle).If you still cannot resolve the issue, open a GitHub Discussion for usage questions, or a bug report via the English / Japanese issue forms.
Please include:
@gurezo/web-serial-rxjs and rxjs versionsSerialSessionStatus / SerialErrorCode (and context if present)