See development-environment.md for OS-specific prerequisite installation and troubleshooting.
See development-environment.md for required build tools and the full recovery steps.
Windows: "Could not find any Python installation to use" (e.g. when building @serialport/bindings-cpp)
See development-environment.md for Python setup and npm/node-gyp troubleshooting.
- Make sure your device has Bluetooth enabled and is in pairing mode
- On macOS: check System Settings > Privacy & Security > Bluetooth
- Try disconnecting fully first, then reconnecting
- If the device picker never appears, restart the app
- Bluetooth adapter not found: ensure Bluetooth is enabled at the OS level. On Linux:
systemctl status bluetoothandrfkill list. On macOS: check System Settings > Bluetooth. On Windows: Settings → Bluetooth & devices. - Device not discovered: make sure the device is in advertising/pairing mode and within range. Try stopping and restarting the scan.
- If BLE is unreliable, prefer Serial (USB) or TCP/HTTP for a stable connection.
- After Noble
connectAsync,mtu=nullis common until the stack finishes ATT MTU negotiation. - A line like
MTU updated: 20comes from the Noblemtuevent. ATT_MTU must be ≥ 23 per spec; the client coerces reported values below 23 to 23 for write sizing (treating odd values such as 20 as a Noble/binding quirk, not a literal 20-octet ATT MTU). A one-time debug line may note the raw value when that happens (not a warning). - Slow NodeDB / large config sync over BLE can still be limited by
@meshtastic/corequeue timing (hundreds of ms between queued packets), not only GATT MTU. Use Log → Analyze for hints, or try USB serial / TCP if throughput matters.
Windows-specific:
- Before connecting to a MeshCore device over BLE, pair it first in Settings → Bluetooth & devices → Add device. Without pairing, the connection appears to succeed but no data is exchanged.
Linux-specific:
- The app uses Web Bluetooth (Chromium's built-in BLE API). You still need a working Bluetooth stack (
systemctl status bluetooth). - Linux BLE uses the in-app Bluetooth picker (triggered from a button click); if no picker appears, restart the app and try Connect again.
- If the Bluetooth adapter isn't detected, check:
systemctl status bluetoothandrfkill list. - MeshCore: After you pick a radio, the app checks
bluetoothctl info <MAC>. If the device is not paired at the OS level, you are prompted for the PIN shown on the device and pairing runs viabluetooth-pairbefore Web Bluetooth finishes connecting. Meshtastic does not use this gate in the same way (it may use PIN123456on the first pairing prompt from Chromium). - If device pairing fails with "Connection attempt failed", try the "Remove & Re-pair Device" button in the app, or manually remove via
bluetoothctl:bluetoothctl # Inside bluetoothctl: remove XX:XX:XX:XX:XX:XX # Replace with your device MAC # Then re-pair from the app
- For Meshtastic devices, the first Chromium pairing attempt may use PIN
123456. For MeshCore, always use the PIN shown on the radio (and the pre-connect prompt when BlueZ reports not paired). - If devices won't pair or connect, power-cycle Bluetooth:
bluetoothctl power off bluetoothctl power on
- MeshCore devices must be in Bluetooth Companion mode. If you still see bonds without a PIN, remove the device in
bluetoothctlor use Remove & Re-pair Device, then connect again.
See development-environment.md for OS-specific serial setup and driver guidance.
See development-environment.md#linux for serial permission recovery steps.
Cause: macOS tags downloads with the com.apple.quarantine extended attribute. For apps that are not signed with a Developer ID and not notarized, Gatekeeper may show "File is damaged and cannot be opened" (or "Mesh-client" is damaged and can't be opened) instead of the usual unidentified-developer prompt. This is a security / quarantine behavior and is common on Apple silicon for community-built Electron binaries.
Fix:
- Open System Settings → Privacy & Security and scroll to the bottom. If you see "Mesh-client was blocked from use", click Allow to run the app.
- If you don't see the Mesh-client entry in Privacy & Security, or the app still won't open after clicking Allow, strip the quarantine attribute; adjust the path if the app is still under Downloads or another folder:
xattr -r -d com.apple.quarantine /Applications/Mesh-client.appAfter running xattr, check Privacy & Security again (scroll to the bottom); the entry should now appear with an Allow button.
Right-click → Open on first launch can also help in some cases. Background and discussion: jeffvli/feishin#104 (comment).
- macOS 26 (Tahoe) + EXC_BREAKPOINT at launch: electron-builder ad-hoc signing can crash during ElectronMain/V8 init before any app code runs. This repo sets
mac.identity: nullinelectron-builder.ymlso the packaged app is unsigned and avoids that re-sign path; first open may require Right-click → Open or clearing quarantine (macOS: File is damaged… above). For notarized releases, set a real Developer ID inmac.identityand retest on macOS 26. See electron#49522 and electron-builder#9396. - This may also be a native module signing issue; try rebuilding:
pnpm run dist:mac - If building from source: make sure
pnpm installcompleted without errors
- The Bluetooth connection can drop silently; click Disconnect, then Connect again
- For serial: the USB cable may have been bumped; reconnect
Open the Log panel (right rail), enable debug if needed, reproduce the problem, then click Analyze. The app scans recent buffered log lines for patterns (BLE, serial, TCP, MQTT, handshake timeouts, etc.) and lists suggested next steps. This complements export/delete: use it before filing an issue so you have concrete log context. Analysis is heuristic; treat recommendations as hints, not guarantees.
[permissions] checkHandler: media → denied and web-app-installation → denied are expected. The app only uses serial and geolocation; media and web-app-installation are intentionally denied.
electron-builder publishes to GitHub when it thinks it's in CI. Local builds use --publish never so artifacts land in release/ without a token. Tag releases use pnpm run dist:mac:publish (and :linux:publish / :win:publish) with GH_TOKEN set; see .github/workflows/release.yaml.
Node deprecates spawn(..., { shell: true }) with an args array. This project carries the packaging workaround via pnpm patchedDependencies on transitive packages used by the Electron build path. Re-run pnpm install if you upgrade electron-builder or its transitive packaging deps and the warning returns.
npm's JSON tree lists hoisted packages with many duplicate refs (one per edge). That's expected and not something you need to fix. The patched packaging dependency path keeps that summary at debug only so normal dist:* runs stay quiet. To see it: DEBUG=electron-builder pnpm dlx electron-builder --mac (or your usual dist command).
The app uses npm package overrides to force follow-redirects and cacheable-request onto versions that use the WHATWG URL API, which removes this warning. To trace the source of any deprecation, run:
pnpm run trace-deprecationCause: @stoprocent/noble (or @serialport/bindings-cpp) was compiled for a different Electron ABI; common after an Electron or Node version change.
Fix: Run pnpm install (the postinstall script rebuilds native modules for the correct ABI automatically).
- If you still see dlopen errors after switching machines or OSes, delete
node_modulesand run a cleanpnpm install. - Windows: Also ensure the Visual C++ Redistributable is installed.
Symptoms
Attempting to build a module with a space in the pathduringpnpm run dist:win(orpnpm run rebuild).EPERM: operation not permittedwhen the rebuild tries to replace a locked.nodefile.
Cause
- Spaces in the project path: node-gyp is unreliable when the repo lives under a path with spaces (e.g.
C:\Users\Joey Stanford\mesh-client). This can surface as "Attempting to build a module with a space in the path", "Could not find any Visual Studio installation to use", or EPERM. See node-gyp#65. - EPERM on unlink: Something on Windows still has the
.nodefile open (anothernode/electronprocess, antivirus/Windows Defender scanning the file, or a stuck handle).
Fix
- Use a path without spaces (strongly recommended): clone or copy the repo to e.g.
C:\dev\mesh-client, thenpnpm installandpnpm run dist:winfrom there. - Clear the lock before rebuild: quit any running Mesh-Client/Electron dev instances, then delete the affected
buildfolder undernode_modulesand retry. - Rebuild then dist:
pnpm run rebuild; if that succeeds, runpnpm run dist:win.
CI builds avoid both issues by using short paths and clean agents; local Windows builds need the same constraints.
Symptoms
- Explorer or the compiler shows error 0x80010135 with Path too long, often on a
*.lastbuildstatefile undernode_modules. bluetooth_hci_socketin the name points at@stoprocent/bluetooth-hci-socket(a native dependency of@stoprocent/noble). MSBuild writes build state under very deep paths; together with a long clone directory, the full path can exceed the legacy ~260 character Win32 limit.
Fix (use one or more)
- Shorten the repo path (most reliable): clone or copy the project to a shallow path such as
C:\dev\mesh-clientinstead of e.g.C:\Users\…\Documents\GitHub\org\mesh-client. - Enable long paths in Git (helps clones/checkouts):
git config --global core.longpaths true, then re-clone or ensure no stuck long paths in the worktree. - Enable Win32 long paths in Windows (Windows 10 1607+): Settings → System → About → Advanced system settings → Environment Variables is not the usual switch; use Local Group Policy → Computer Configuration → Administrative Templates → System → Filesystem → Enable Win32 long paths, or the registry DWORD
LongPathsEnabled = 1underHKLM\SYSTEM\CurrentControlSet\Control\FileSystem(admin rights; reboot may be required). See Microsoft: Maximum Path Length Limitation. pnpm run dist:winalready runs a hoistedpnpm installto shortennode_modulesdepth before packaging; ifpnpm install/pnpm run rebuildfails earlier with this error, try the short path and long-path OS settings first, or temporarily:pnpm install --config.node-linker=hoistedfrom a short root path.- Packaged app (
dist:win): the build embeds a Windows application manifest withlongPathAwareso the installed Mesh-client.exe can use long paths when the machine has long paths enabled (registry / policy). That helps runtime paths inside the app; it does not shortennode_modulesduringpnpm installon the build machine—CI and developers still benefit from short clone paths for native rebuilds.
Error: "Database directory is not writable: <path>"
Cause: File permissions on the app's userData directory are too restrictive.
Fix:
- Mac/Linux:
chmod 755 ~/Library/Application\ Support/mesh-client(or~/.config/mesh-clienton Linux) - Windows: Right-click
%APPDATA%\mesh-client→ Properties → Security → grant your user Full Control
meshtastic.local (or any .local hostname) not found on Windows:
Windows does not have built-in mDNS resolution. .local hostnames require Bonjour (installed with iTunes or Apple Devices). Install either:
- iTunes: includes Bonjour automatically
- Bonjour Print Services for Windows: standalone Bonjour installer
Alternatively, enter the device's IP address directly instead of its .local hostname.
A yellow warning is shown below the address input on Windows as a reminder.
IPv6 address format:
Bare IPv6 addresses (e.g. fe80::1) must be wrapped in brackets when entered in the HTTP address field: [fe80::1]. The app normalises bare addresses automatically, but entering [fe80::1]:443 (with port) is the most reliable form.
Cause: Broker unreachable, bad credentials, or wrong port.
Fix: Verify the broker URL, port (default 1883, or 8883 for TLS), and username/password. Check that your firewall allows outbound connections on the broker port.
Cause: Topic permission denied on the broker, or wildcards not allowed by the broker ACL.
Fix: Confirm the broker's ACL allows your client to subscribe to the configured topic prefix.
Cause: Wireless interference, broker downtime, or token issues (LetsMesh/Colorado Mesh).
Fix:
- Check your WiFi/signal strength
- Verify the broker is online
- For LetsMesh/Colorado Mesh: re-import your MeshCore identity to refresh the token
- Enable debug logs to see the disconnect reason
Cause: LetsMesh and Colorado Mesh are publish-only brokers; you can send packets to the mesh but won't receive other users' traffic over MQTT. The connection is real, but incoming messages are limited.
Fix: Expected behavior for public brokers. For two-way MQTT, use a different broker or connect via BLE/Serial.
Cause: JWT tokens expire after 1 hour.
Fix: Re-import your MeshCore config JSON in the Radio tab, or paste your v1_ public key in the MQTT username field to regenerate a token.
Cause: Wrong broker URL, port, or firewall blocking the connection.
Fix:
- Verify the server URL and port match your broker's settings
- Check that port 1883 (or 8883/443 for TLS/WebSocket) is allowed through your firewall
- For WebSocket brokers (port 443), ensure "Use WebSocket" is enabled in the MQTT settings
Cause: The reconnect card appeared, but the browser lost the cached device handle; for example, the app was fully quit and relaunched.
Fix: Click Forget this device on the reconnect card and pair fresh using the Bluetooth picker.
Cause: Browser geolocation was denied, or the device has no GPS fix yet.
Fix:
- Grant location permission when prompted by the app.
- Or set coordinates manually via the Radio tab → Fixed Position.
- Note: The IP-geolocation fallback (ipwho.is) provides city-level accuracy only; not suitable for position broadcasting. If the service is unreachable, "Location unavailable" is shown.
Cause: An unhandled React render error, usually from a corrupt or unexpected database value.
Fix: Open the App tab → Clear Database, then restart. If the window never loads at all, delete the SQLite file manually:
- Mac:
~/Library/Application Support/mesh-client/ - Windows:
%APPDATA%\mesh-client\ - Linux:
~/.config/mesh-client/
Cause: Known Electron/Chromium quirk on macOS when the first responder is a text field (e.g. the chat input). The native menu bridge logs this; it does not affect behavior.
Fix: None required; safe to ignore. Copy/paste and other edit actions still work.
The app functions fully offline; this is not a critical error. If "Update check failed" appears in the console, verify network connectivity. Update checks are rate-limited by the GitHub API and may silently skip when the limit is reached. The footer shows Update error when a check fails; use Check for updates in the app menu or retry from the footer when applicable.
Basemap tiles: The map background uses OpenStreetMap raster tiles loaded over HTTPS. The TileLayer is defined in MapPanel.tsx. Without internet access, new tiles cannot be fetched, so the basemap may look blank, gray, or incomplete, or show only tiles previously cached by the embedded browser (caching is best-effort and not guaranteed).
Overlays: Node markers, polylines, position trails, and other vector layers are separate from the tile layer. If nodes have latitude/longitude (from RF, MQTT, SQLite, or your session), those overlays can still render on top of a missing or partial basemap.
Your position offline: Use device GPS when available, Fixed Position on the Radio tab, or static coordinates in app/GPS settings. See GPS "Location unavailable" or stuck on the map above for IP-based fallbacks and manual entry. Positions heard over the mesh do not require internet.
With Wi‑Fi off or airplane mode on, using a packaged build if possible:
- Confirm the app window loads and core tabs work; connect via USB serial or BLE to a local radio if you need RF features.
- Open the Map tab: expect missing or stale basemap tiles as described above; markers and trails may still appear when position data exists.
- A non-fatal update check message in the console is expected without WAN; see Update check fails / footer update status above.
Cause: Diagnostic rows (routing + RF) are snapshotted to localStorage so a restart doesn't wipe the table.
Fix: This is expected; rows refresh as new packets arrive. Use Stop restoring on next launch on the banner to clear the snapshot, or use App tab → Reset Diagnostics to clear in-memory rows and related state.
Cause: RF rows age out faster (default 1 h) than routing rows (default 24 h); very old rows are pruned by timestamp.
Fix: In Network Diagnostics → Display Settings, adjust diagnostic row max age (hours). Or reset diagnostics from the App tab and let the mesh repopulate.
Cause: Signal strength is only available for direct (0-hop) RF neighbors. Multi-hop and MQTT-heard nodes have no client-side signal strength.
Fix: Not a bug; use SNR/last heard and routing diagnostics instead for those paths.
Cause: The remote node has no environment sensors, or the request timed out before the node responded.
Fix: Not all nodes support environment telemetry. The error is shown inline in the node detail modal and is safe to ignore.
Cause: The button is only shown for Repeater-type contacts (contact type 2). Chat and Room contacts do not support the neighbor query command.
Fix: Open the node detail modal for a Repeater node (shown as "Repeater" in the hardware model field).
Bluetooth:
- The device must be flashed as Companion Bluetooth (the default BLE flashing mode).
- The device must be paired with your computer before connecting:
- Windows: Pair first in Settings → Bluetooth & devices → Add device, then connect from the app.
- Linux: Use
bluetoothctl pair <MAC>first, or let the app handle the pairing prompt. See BLE known issues for detailed steps.
- Try in the official MeshCore app first: if the device connects there, it will work in Mesh-Client.
- If Bluetooth fails, try serial (USB) or HTTP as alternatives.
USB (Serial):
- The device must be flashed as Companion USB (not BLE-only firmware).
- If the serial port is not detected, see Serial port not detected.
HTTP (WiFi):
- The device must be flashed as Companion HTTP (not BLE-only firmware).
- If
meshtastic.localis not resolved, see HTTP / WiFi connection issues.
Cause: Nodes you only hear on the mesh; but that do not have your node in their contact list; are sometimes called foreign or one-way contacts. MeshCore firmware may not answer Trace Route (node detail) or Ping trace (Repeaters panel) for those peers, so the app waits until the trace/ping timeout with no TraceData response. You may see Trace route timed out in the node detail modal or an error toast from Ping trace.
Fix: When possible, exchange contact adds so the remote node lists you as a contact. If you cannot add them (or they never add you), treat the timeout as expected, not a Mesh-Client defect when the radio never returns a result.
Cause: The packet logger publishes to {prefix}/{pubKey}/packets, but you're viewing the packets somewhere that doesn't receive published MQTT messages.
Fix:
- The app publishes to
meshcore/{IATA}/{pubKey}/packets(e.g.,meshcore/DEN/AABBCCDDEEFF001122/packets) - Use an external MQTT client (like MQTT Explorer, mosquitto_sub, or your broker's dashboard) to subscribe and view the packets
- For Colorado Mesh, subscribe to
meshcore/DEN/+/packets/# - For LetsMesh/MeshMapper, subscribe to
meshcore/test/+/packets/# - Verify your broker ACL allows publishing to
packets/topics - Check the Log panel for "Published RF packet" entries to confirm packets are being sent