Skip to content

v1.3.7 regression: crash loop on Linux during network interface discovery (ip -o link show parser) #69

@eamondowling

Description

@eamondowling

Analysis

Ciao v1.3.7 crashes with an unhandled rejection during startup on Linux systems. The regression correlates with the switch from ip neigh show to ip -o link show in NetworkManager.getLinuxNetworkInterfaces() (commit f53c664f). The crash produces unhandled rejection dumps with no message or stack trace, suggesting an error is being swallowed or propagated uncaught somewhere in the startup promise chain.

Expected Behavior

Ciao should start without crashing on Linux. Network interface discovery should complete successfully, interfaces should be bound, and the mDNS responder should begin advertising services. The Node.js process should remain stable.

Steps To Reproduce

Steps to Reproduce

  1. Install @homebridge/ciao@1.3.7 on a Linux system (e.g., Raspberry Pi OS, Debian)
  2. Create a basic service advertisement script:
const ciao = require('@homebridge/ciao');
const responder = ciao.getResponder();
const service = responder.createService({
  name: 'Test',
  type: 'test',
  protocol: 'TCP',
  port: 12345
});
service.advertise();
  1. Run the script with Node.js v22: node test.js
  2. Observe that the process exits with an unhandled rejection almost immediately
  3. Alternatively, run under a process manager with auto-restart (e.g., systemd with Restart=always) and observe a crash loop every ~45-50 seconds

Notes:

  • Same script with @homebridge/ciao@1.3.6 starts successfully
  • Crash correlates with systems where ip -o link show output contains interface lines with fewer than 3 space-separated parts, or where interface names contain spaces

Logs

## Logs / Crash Dumps

The crash manifests as an **unhandled rejection** with no message or stack trace. Node.js stability dumps are empty:


{
  "version": 1,
  "generatedAt": "2026-04-25T23:41:08.418Z",
  "reason": "unhandled_rejection",
  "process": {
    "pid": 532641,
    "platform": "linux",
    "arch": "arm64",
    "node": "22.22.2",
    "uptimeMs": 45337
  },
  "host": {
    "hostname": "<redacted>"
  },
  "snapshot": {
    "generatedAt": "2026-04-25T23:41:08.419Z",
    "capacity": 1000,
    "count": 0,
    "dropped": 0,
    "events": [],
    "summary": {
      "byType": {}
    }
  }
}


**Pattern:** ~20 crashes over 16 minutes, ~48 seconds apart. Matches systemd `Restart=always` + `RestartSec=5`.

**Journal excerpt (filtered):**

Apr 26 21:04:50 node[1022784]: 2026-04-26T21:04:50.925-04:00 [ws] ⇄ res ✓ channels.status 202ms

No ciao-specific logs present; console output may be suppressed by OpenClaw's noise filter.

---

## Critique of the `ip -o link show` Parser (v1.3.7)

The v1.3.7 change from `ip neigh show` to `ip -o link show` introduced a new parsing path. While bounds checking exists, the parser makes assumptions about output format that may not hold across all Linux configurations.

### Example `ip -o link show` output:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000\    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000\    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff


### Parsing logic (v1.3.7):

const parts = lines[i].trim().split(NetworkManager.SPACE_PATTERN);
if (parts.length < 3 || !parts[1] || !parts[2]) {
    continue;
}
const interfaceName = parts[1].replace(/:$/, "");
if (parts[2].includes("LOOPBACK")) {
    continue;
}


### Potential edge cases:
- **Interface names with spaces**: `ip -o` wraps them in braces or escapes them differently than `ip neigh show`
- **Virtual interfaces**: Docker bridges, WireGuard, Tailscale interfaces may have unusual formatting
- **Lines with fewer parts**: Some kernel-generated lines or virtual interfaces may produce `parts.length < 3`
- **The `\    ` continuation**: `ip -o link show` uses `\` followed by spaces/tabs to indicate line continuation. The `os.EOL` split may not handle this correctly if the continuation creates embedded newlines

The v1.3.6 `ip neigh show` approach used `parts[5]` for interface names (`dev <iface>`), which was more reliable because the `dev` keyword provided a stable anchor regardless of line length.

Configuration

There's no ciao-level config file to edit.

Environment

Package: @homebridge/ciao | Version: 1.3.7 (regression from 1.3.6) | Platform: Linux (arm64, Debian-based) | Node.js: v22.22.2

Process Supervisor

systemd

Additional Context

Process Supervisior

This was discovered in an OpenClaw plugin context.

Expected Behavior

Gracefully skip malformed/short lines or handle missing parts[2] without crashing.

Actual Behavior

Unhandled rejection during startup with empty message/stack. Process crashes immediately, triggering systemd restart loop (~48s cycle).

Suggested Fix

Remove it entirely and say "Root cause unclear; needs investigation"

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions