Skip to content

Commit b969232

Browse files
sym-botclaude
andcommitted
fix(cli): 0.7.1 — Windows sym ask exit-crash + sym groups dns-sd ENOENT
Found by COO's cross-machine Windows test of 0.7.0 (install + daemon lifecycle + cross-machine mesh all passed; these were the only 2 bugs): - sym ask: broadcast socket closed with .end() left a named-pipe handle mid-close; process.exit then tripped a libuv UV_HANDLE_CLOSING assertion on Windows (0xC0000409). Now socket.destroy() + clear the timer so the handle is fully gone before exit. - sym groups: spawn dns-sd ENOENT on Windows (no Apple Bonjour) now degrades gracefully — node still meshes via bonjour-service; sym join works directly. Also fixed a timer-ordering (TDZ) bug in the discovery path. Full suite 168/168; sym ask/groups exit cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a9b50fa commit b969232

4 files changed

Lines changed: 47 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
> **Note:** Versions 0.3.26 – 0.3.55 were released as git tags without changelog entries. Changelog resumes at 0.3.56 below.
44
5+
## 0.7.1
6+
7+
### Fixed (Windows — found by COO's cross-machine test on a real Windows box)
8+
9+
- **`sym ask` crashed on exit on Windows** with a libuv assertion (`!(handle->flags & UV_HANDLE_CLOSING)`, `win/async.c`, exit `0xC0000409`). The best-effort broadcast socket was closed with `socket.end()`, which leaves the named-pipe handle mid-close; when the command then `process.exit`s, Windows aborts. Now `socket.destroy()` tears the handle down fully (and the timeout is cleared) before exit. Mac/Linux unaffected either way.
10+
- **`sym groups` errored with `spawn dns-sd ENOENT` on Windows** (Apple Bonjour's `dns-sd` isn't installed). It now degrades gracefully — a clear message that LAN group *enumeration* needs the tool, while noting the node still meshes via the bundled pure-JS `bonjour-service` and you can `sym join <name>` directly. Also fixed a timer-ordering bug in the discovery path.
11+
12+
*(Cross-machine mesh, install, and daemon lifecycle all PASSED on Windows in 0.7.0 — these two were the only issues.)*
13+
514
## 0.7.0
615

716
### Added

bin/sym.js

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -219,19 +219,34 @@ function cmdGroups() {
219219
const platform = process.platform;
220220
const cmd = (platform === 'linux') ? 'avahi-browse' : 'dns-sd';
221221
const argv = (platform === 'linux') ? ['-t', '-a', '-p'] : ['-B', '_services._dns-sd._udp', 'local.'];
222+
// Graceful when the browse tool is absent (e.g. Windows without Apple
223+
// Bonjour's dns-sd — the node itself still discovers peers via the
224+
// bundled pure-JS bonjour-service, so meshing works; only this LAN-wide
225+
// group *enumeration* needs the CLI tool).
226+
const unavailable = (e) => {
227+
if (e && e.code === 'ENOENT') {
228+
console.log(`Live group discovery isn't available here — '${cmd}' isn't installed.`);
229+
if (platform === 'win32') console.log('(Windows: LAN group discovery needs Apple Bonjour. Your node still meshes fine.)');
230+
else if (platform === 'linux') console.log('(Install avahi-utils for discovery: sudo apt install avahi-utils)');
231+
} else {
232+
console.error(`group discovery failed: ${(e && e.message) || e}`);
233+
}
234+
console.log(`Your group: ${readGroup()} (${groupServiceType(readGroup())}) · switch with: sym join <name>`);
235+
};
236+
222237
let child;
223238
try { child = spawn(cmd, argv, { stdio: ['ignore', 'pipe', 'pipe'] }); }
224-
catch (e) {
225-
console.error(`Could not run discovery ('${cmd}'): ${e.message}` +
226-
(platform === 'linux' ? '\nInstall avahi-utils: sudo apt install avahi-utils' : ''));
227-
return;
228-
}
239+
catch (e) { return unavailable(e); }
240+
let errored = false;
241+
let timer = null;
242+
child.on('error', (e) => { errored = true; if (timer) clearTimeout(timer); unavailable(e); });
243+
if (!child.stdout) return; // ENOENT path on some platforms leaves no stream
229244
const out = [];
230245
child.stdout.on('data', (c) => out.push(c));
231-
child.on('error', (e) => console.error(`discovery failed: ${e.message}`));
232-
const timer = setTimeout(() => { try { child.kill('SIGTERM'); } catch {} }, 2200);
246+
timer = setTimeout(() => { try { child.kill('SIGTERM'); } catch {} }, 2200);
233247
child.on('close', () => {
234248
clearTimeout(timer);
249+
if (errored) return; // already reported via the error handler
235250
const text = Buffer.concat(out).toString('utf8');
236251
const typeRe = /_([a-z0-9][a-z0-9-]+)\._tcp/gi;
237252
const seen = new Set();
@@ -647,7 +662,18 @@ function broadcastQuestion(question) {
647662
return new Promise((resolve) => {
648663
if (!isDaemonRunning()) return resolve(false);
649664
let settled = false;
650-
const finish = (v) => { if (!settled) { settled = true; try { socket.end(); } catch {} resolve(v); } };
665+
let timer = null;
666+
// socket.destroy() (not .end()) — fully tears down the handle synchronously
667+
// so no named-pipe handle is left mid-close when `sym ask` later exits.
668+
// On Windows, exiting with a half-closed handle trips a libuv assertion
669+
// (UV_HANDLE_CLOSING, win/async.c) and aborts with 0xC0000409.
670+
const finish = (v) => {
671+
if (settled) return;
672+
settled = true;
673+
if (timer) clearTimeout(timer);
674+
try { socket.removeAllListeners('data'); socket.destroy(); } catch {}
675+
resolve(v);
676+
};
651677
const socket = net.createConnection(SOCKET_PATH, () => {
652678
socket.write(JSON.stringify({ type: 'register', name: 'sym-cli' }) + '\n');
653679
});
@@ -669,7 +695,7 @@ function broadcastQuestion(question) {
669695
}
670696
});
671697
socket.on('error', () => finish(false));
672-
setTimeout(() => finish(false), 2000);
698+
timer = setTimeout(() => finish(false), 2000);
673699
});
674700
}
675701

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sym-bot/sym",
3-
"version": "0.7.0",
3+
"version": "0.7.1",
44
"description": "Infrastructure and protocol for multi-agent collective intelligence",
55
"main": "lib/node.js",
66
"bin": {

0 commit comments

Comments
 (0)