fix(cli): honor CLIDE_SOCK as an explicit target, fail loudly if dead (T-247)

The `clide` shell client only ever computed the socket path from the
workspace's FNV-1a hash — it never read CLIDE_SOCK, so the var the app
exports to spawned agents was a no-op and a bogus CLIDE_SOCK was silently
ignored (it still hit the discovered instance). Now: when CLIDE_SOCK is set
the client connects to it directly, beating discovery, and a dead socket
aborts with EX_UNAVAILABLE rather than falling back to a different instance
(the split-brain footgun). Unset → the deterministic per-workspace path
(D-70) as before. The server keeps deterministic binding; this is a
client-side targeting override only.

Also fixes a pre-existing -Wstringop-truncation warning in
find_workspace_root (strncpy+manual-null → snprintf).

Tests: the e2e suite now clears the inherited CLIDE_SOCK so discovery tests
stay hermetic (the suite may run inside a clide instance), plus two new
tests — a valid CLIDE_SOCK pins from a non-git dir, a dead one fails loudly
and returns no data.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 08:11:55 +02:00
co-authored by Claude Opus 4.8
parent 3cbe0e6ec6
commit 6af980f31c
5 changed files with 82 additions and 14 deletions
+40 -3
View File
@@ -71,8 +71,12 @@ void main() {
}
});
// Clear any inherited CLIDE_SOCK so these tests exercise workspace discovery
// hermetically — the suite may run *inside* a clide instance, which exports
// CLIDE_SOCK to child processes (T-247). Empty string reads as unset to the
// client, which then falls back to per-workspace discovery.
Future<ProcessResult> runCli(List<String> argv) {
return Process.run(binaryPath, argv, workingDirectory: workspaceRoot.path);
return Process.run(binaryPath, argv, workingDirectory: workspaceRoot.path, environment: const {'CLIDE_SOCK': ''});
}
group('clide-cli (T-126)', () {
@@ -93,7 +97,7 @@ void main() {
}
final outside = Directory.systemTemp.createTempSync('clide-no-git-');
addTearDown(() => outside.deleteSync(recursive: true));
final r = await Process.run(binaryPath, ['status'], workingDirectory: outside.path);
final r = await Process.run(binaryPath, ['status'], workingDirectory: outside.path, environment: const {'CLIDE_SOCK': ''});
expect(r.exitCode, 64);
expect(r.stderr.toString(), contains('git repository'));
});
@@ -141,7 +145,12 @@ void main() {
markTestSkipped('cc not available');
return;
}
final proc = await Process.start(binaryPath, ['tail', '--events', '--filter', 'pane'], workingDirectory: workspaceRoot.path);
final proc = await Process.start(
binaryPath,
['tail', '--events', '--filter', 'pane'],
workingDirectory: workspaceRoot.path,
environment: const {'CLIDE_SOCK': ''},
);
addTearDown(() => proc.kill());
final lines = <String>[];
final sub = proc.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen(lines.add);
@@ -166,5 +175,33 @@ void main() {
expect(concatenated, contains('"kind":"spawned"'));
expect(concatenated, contains('"kind":"closed"'));
});
test('CLIDE_SOCK pins to that instance, beating workspace discovery (T-247)', () async {
if (!hasCC) {
markTestSkipped('cc not available');
return;
}
// From a NON-git dir (where discovery would fail), an explicit CLIDE_SOCK
// still connects — it's the explicit target.
final outside = Directory.systemTemp.createTempSync('clide-sock-');
addTearDown(() => outside.deleteSync(recursive: true));
final r = await Process.run(binaryPath, ['ping'], workingDirectory: outside.path, environment: {'CLIDE_SOCK': server.socketPath});
expect(r.exitCode, 0, reason: 'stderr: ${r.stderr}');
expect((jsonDecode(r.stdout.toString().trim()) as Map)['pong'], isTrue);
});
test('a dead CLIDE_SOCK fails loudly, never falling back to discovery (T-247)', () async {
if (!hasCC) {
markTestSkipped('cc not available');
return;
}
// Run from the REAL workspace — discovery WOULD succeed — to prove the
// bogus explicit target aborts instead of silently hitting another instance.
final bogus = '${workspaceRoot.path}/DOES_NOT_EXIST.sock';
final r = await Process.run(binaryPath, ['ping'], workingDirectory: workspaceRoot.path, environment: {'CLIDE_SOCK': bogus});
expect(r.exitCode, isNot(0));
expect(r.stderr.toString(), contains('cannot connect'));
expect(r.stdout.toString().trim(), isEmpty, reason: 'must not return data from a different instance');
});
});
}