a73x

d39f9927

test: the handoff stderr check asks whether fd 2 is a pipe

a73x   2026-09-03 19:13

Commit message
test: the handoff stderr check asks whether fd 2 is a pipe

The fixture read the link target of /dev/fd/2 and looked for 'pipe:'. On
Darwin /dev/fd is the fdesc filesystem and its entries are not symlinks,
so readlink answered nothing, the recorded file was empty and the check
graded an empty string. 'test -p' stats the path on both systems and
reports the underlying object's type, which is the claim the test makes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SakwJEwD9dXBoRP5kWbemW

src/client/client.zig
Old New
@@ -2378,9 +2378,15 @@ test "openHandoff: the handoff ssh's stderr is a pipe, and only `narrate` relays
2378 // A hosts line naming a box that is down used to put ssh's `No route to 2378 // A hosts line naming a box that is down used to put ssh's `No route to
2379 // host` onto the wall's alternate screen every poll, because the child's 2379 // host` onto the wall's alternate screen every poll, because the child's
2380 // stderr was INHERITED. It is a pipe mux reads now, whoever dialled. The 2380 // stderr was INHERITED. It is a pipe mux reads now, whoever dialled. The
2381 // fake records where its stderr POINTED, off `/dev/fd/2`, so "piped" is 2381 // fake records what KIND of file its stderr is, off `/dev/fd/2`, so
2382 // exact — `/dev/fd` because every OS this builds for has it and the 2382 // "piped" is exact — `/dev/fd` because every OS this builds for has it
2383 // Linux-only spelling would have to be ported alongside the test. 2383 // and the Linux-only spelling would have to be ported alongside the test.
2384 // Asked with `test -p` rather than by reading a link target: on Linux
2385 // `/dev/fd/2` is a symlink to `pipe:[N]` and on Darwin it is an entry of
2386 // the fdesc filesystem that is not a symlink at all, so `readlink` there
2387 // answers nothing and the check passed on an empty file. `test -p` stats
2388 // the path on both and reports the underlying object's type, which is the
2389 // claim being made.
2384 // BOTH values of `asked`, because the rule is the spawn's. 2390 // BOTH values of `asked`, because the rule is the spawn's.
2385 const alloc = std.testing.allocator; 2391 const alloc = std.testing.allocator;
2386 var stdin = try FakeStdin.install(""); 2392 var stdin = try FakeStdin.install("");
@@ -2394,7 +2400,7 @@ test "openHandoff: the handoff ssh's stderr is a pipe, and only `narrate` relays
2394 2400
2395 var script_buf: [1024]u8 = undefined; 2401 var script_buf: [1024]u8 = undefined;
2396 const script = try std.fmt.bufPrint(&script_buf, 2402 const script = try std.fmt.bufPrint(&script_buf,
2397 \\readlink /dev/fd/2 > {[d]s}/e 2403 \\if [ -p /dev/fd/2 ]; then echo pipe > {[d]s}/e; else echo "not a pipe" > {[d]s}/e; fi
2398 \\printf 'boom: no route\n' >&2 2404 \\printf 'boom: no route\n' >&2
2399 \\exit 1 2405 \\exit 1
2400 , .{ .d = tmp.path() }); 2406 , .{ .d = tmp.path() });
@@ -2416,7 +2422,7 @@ test "openHandoff: the handoff ssh's stderr is a pipe, and only `narrate` relays
2416 2422
2417 var err_buf: [std.fs.max_path_bytes]u8 = undefined; 2423 var err_buf: [std.fs.max_path_bytes]u8 = undefined;
2418 const on_err = try shimSaid(tmp.path(), "e", &err_buf); 2424 const on_err = try shimSaid(tmp.path(), "e", &err_buf);
2419 try std.testing.expect(std.mem.startsWith(u8, on_err, "pipe:")); 2425 try std.testing.expectEqualStrings("pipe", on_err);
2420 // Kept in every case: the picker row is painted from a dial 2426 // Kept in every case: the picker row is painted from a dial
2421 // nobody narrated, which is the whole point of keeping it here 2427 // nobody narrated, which is the whole point of keeping it here
2422 // rather than letting the bytes fall out onto a screen. 2428 // rather than letting the bytes fall out onto a screen.