a73x

8cc75997

refactor: one owner for "this shell is standing in that session"

a73x   2026-08-29 10:01

Commit message
refactor: one owner for "this shell is standing in that session"

`mux_main.insideThisSession` and `wallview.showsSelf` answered the same
question with the same two `mem.eql`s — the second even said "Mirrors
`mux_main.insideThisSession`" in its doc block. The CLI's entry attach now
calls `wallview.showsSelf` (mux_main already imported wallview), passing
`.{ .sock = sock_path }` for the local-socket-only target the wall's
`client.Target` spells as a union arm.

The rationale the CLI copy carried — emptied counts as unset, both halves
so session 0 -> session 1 keeps working, string equality on the path is not
a security boundary, and the not-a-unix-socket arm — moves into the
surviving function's body, where it now covers both callers.

mux_main's test dropped: wall_test_wall's `showsSelf` test already asserts
every case it did (both spellings of the default name, another session of
the same daemon, another daemon's socket, null and emptied env, and the
non-socket target the CLI test could not reach). Its title and opening
comment now name both callers.

make check rc 0, 983 tests.

src/cli/mux_main.zig
Old New
@@ -165,32 +165,6 @@ fn agentReachable(path: []const u8) bool {
165 const self_attach_refusal = 165 const self_attach_refusal =
166 "mux: this shell is inside that session (unset " ++ proto.session_env ++ " to override)\n"; 166 "mux: this shell is inside that session (unset " ++ proto.session_env ++ " to override)\n";
167 167
168 /// An inner client repaints its own grid forever, and no chord steers
169 /// back out. Refused before it starts.
170 fn insideThisSession(
171 env_sock: ?[]const u8,
172 env_session: ?[]const u8,
173 sock: ?[]const u8,
174 session: []const u8,
175 ) bool {
176 const es = env_sock orelse return false;
177 const en = env_session orelse return false;
178 // A unix socket path only: a host or quic:// target is a different
179 // daemon whatever its sessions are called.
180 const target = sock orelse return false;
181 // Emptied counts as unset: `MUX_SESSION=` is how a shell overrides an
182 // exported variable it cannot unset, and the refusal names unsetting as
183 // the way out — both spellings of that have to work.
184 if (es.len == 0 or en.len == 0) return false;
185 // Both halves, so session 0 attaching to session 1 of the same daemon
186 // keeps working. String equality on the path: a symlinked or relatively
187 // spelled `--sock` for the same socket evades this, accepted, because
188 // this guards the mistake people make (typing `mux` in a mux shell) and
189 // is not a security boundary.
190 return std.mem.eql(u8, es, target) and
191 std.mem.eql(u8, en, proto.resolveName(session));
192 }
193
194 /// The attach line, read off the struct: the field's type is the flag's 168 /// The attach line, read off the struct: the field's type is the flag's
195 /// arity and its name is the flag's spelling. What a flag MEANS stays here, 169 /// arity and its name is the flag's spelling. What a flag MEANS stays here,
196 /// in the post-checks below. 170 /// in the post-checks below.
@@ -447,11 +421,11 @@ fn attachLocal(
447 // the wall and never come back through here, so focusing from session 0 421 // the wall and never come back through here, so focusing from session 0
448 // to session 1 keeps working. The picker's `a` is the one chord that 422 // to session 1 keeps working. The picker's `a` is the one chord that
449 // takes a spelling, and it runs `wallview.showsSelf` itself. 423 // takes a spelling, and it runs `wallview.showsSelf` itself.
450 if (insideThisSession( 424 if (wallview.showsSelf(
425 .{ .sock = sock_path },
426 session,
451 std.posix.getenv(proto.sock_env), 427 std.posix.getenv(proto.sock_env),
452 std.posix.getenv(proto.session_env), 428 std.posix.getenv(proto.session_env),
453 sock_path,
454 session,
455 )) { 429 )) {
456 std.debug.print("{s}", .{self_attach_refusal}); 430 std.debug.print("{s}", .{self_attach_refusal});
457 return 2; 431 return 2;
@@ -1181,36 +1155,6 @@ test "parseArgs: no --session means the empty wire name (older-daemon compat)" {
1181 try std.testing.expectEqualStrings("", s.attach.session); 1155 try std.testing.expectEqualStrings("", s.attach.session);
1182 } 1156 }
1183 1157
1184 test "insideThisSession: only the exact socket-and-session pair is the loop" {
1185 const sock = "/run/user/1000/muxd.sock";
1186
1187 // The incident, both spellings of the default session: `mux` typed in a
1188 // shell of session 0, and `mux --session 0` typed in the same shell.
1189 try std.testing.expect(insideThisSession(sock, "0", sock, ""));
1190 try std.testing.expect(insideThisSession(sock, "0", sock, "0"));
1191 try std.testing.expect(insideThisSession(sock, "work", sock, "work"));
1192
1193 // A different session of the SAME daemon is the useful case and must
1194 // stay allowed — only the self-pair feeds its own paint back.
1195 try std.testing.expect(!insideThisSession(sock, "0", sock, "1"));
1196 try std.testing.expect(!insideThisSession(sock, "work", sock, ""));
1197
1198 // Same session name, different daemon: names are per-daemon, so this is
1199 // two unrelated sessions that happen to agree on a word.
1200 try std.testing.expect(!insideThisSession(sock, "0", "/run/user/1000/other.sock", "0"));
1201
1202 // Nothing planted, or emptied to override: no refusal either way.
1203 try std.testing.expect(!insideThisSession(null, "0", sock, "0"));
1204 try std.testing.expect(!insideThisSession(sock, null, sock, "0"));
1205 try std.testing.expect(!insideThisSession("", "0", sock, "0"));
1206 try std.testing.expect(!insideThisSession(sock, "", sock, "0"));
1207
1208 // A host or quic:// target has no unix socket to compare, and a remote
1209 // daemon is a different daemon whatever its sessions are called — the
1210 // call sites pass null for those, so null must never refuse.
1211 try std.testing.expect(!insideThisSession(sock, "0", null, "0"));
1212 }
1213
1214 // Forces semantic analysis of every pub decl under `zig build test`, so an 1158 // Forces semantic analysis of every pub decl under `zig build test`, so an
1215 // unreferenced decl must at least compile (the silent-module-loss hazard, 1159 // unreferenced decl must at least compile (the silent-module-loss hazard,
1216 // decisions.md). Pub decls only: std.meta.declarations sees nothing private. 1160 // decisions.md). Pub decls only: std.meta.declarations sees nothing private.
src/server/server.zig
Old New
@@ -900,7 +900,7 @@ pub const Server = struct {
900 900
901 // After extra_env, not before: this is the daemon's identity, not an 901 // After extra_env, not before: this is the daemon's identity, not an
902 // option, and a client reads it back to refuse attaching to the very 902 // option, and a client reads it back to refuse attaching to the very
903 // session it is running inside (mux_main.insideThisSession). A caller 903 // session it is running inside (`wallview.showsSelf`). A caller
904 // that could overwrite it could hand a shell a lie about where it is. 904 // that could overwrite it could hand a shell a lie about where it is.
905 // One plan serves every session, so only the socket belongs here — 905 // One plan serves every session, so only the socket belongs here —
906 // the session name differs per session and createSession appends it. 906 // the session name differs per session and createSession appends it.
src/tui/wall_test_wall.zig
Old New
@@ -475,7 +475,7 @@ test "labelText: the state word survives truncation at every width" {
475 if (widest.len > buf.len) return error.TwoDigitWidestBarOverranItsBuffer; 475 if (widest.len > buf.len) return error.TwoDigitWidestBarOverranItsBuffer;
476 } 476 }
477 477
478 test "showsSelf: the wall never tiles the session the walling shell is standing in" { 478 test "showsSelf: neither the wall nor the CLI attach opens the session the shell is standing in" {
479 const sock = "/run/user/1000/muxd.sock"; 479 const sock = "/run/user/1000/muxd.sock";
480 const target: client.Target = .{ .sock = sock }; 480 const target: client.Target = .{ .sock = sock };
481 481
src/tui/wallview.zig
Old New
@@ -809,20 +809,29 @@ pub fn vanishTile(tiles: []Tile, present: []bool, shared: *Shared, i: usize, to:
809 } 809 }
810 810
811 /// A stripe of the shell's own session paints into the grid it reads. 811 /// A stripe of the shell's own session paints into the grid it reads.
812 /// Mirrors `mux_main.insideThisSession`.
813 pub fn showsSelf( 812 pub fn showsSelf(
814 target: client.Target, 813 target: client.Target,
815 name: []const u8, 814 name: []const u8,
816 env_sock: ?[]const u8, 815 env_sock: ?[]const u8,
817 env_session: ?[]const u8, 816 env_session: ?[]const u8,
818 ) bool { 817 ) bool {
818 // A unix socket path only: a host or quic:// target is a different
819 // daemon whatever its sessions are called.
819 const sock = switch (target) { 820 const sock = switch (target) {
820 .sock => |p| p, 821 .sock => |p| p,
821 else => return false, 822 else => return false,
822 }; 823 };
823 const es = env_sock orelse return false; 824 const es = env_sock orelse return false;
824 const en = env_session orelse return false; 825 const en = env_session orelse return false;
826 // Emptied counts as unset: `MUX_SESSION=` is how a shell overrides an
827 // exported variable it cannot unset, and the refusal names unsetting as
828 // the way out — both spellings of that have to work.
825 if (es.len == 0 or en.len == 0) return false; 829 if (es.len == 0 or en.len == 0) return false;
830 // Both halves, so session 0 attaching to session 1 of the same daemon
831 // keeps working. String equality on the path: a symlinked or relatively
832 // spelled `--sock` for the same socket evades this, accepted, because
833 // this guards the mistake people make (typing `mux` in a mux shell) and
834 // is not a security boundary.
826 return std.mem.eql(u8, es, sock) and std.mem.eql(u8, en, proto.resolveName(name)); 835 return std.mem.eql(u8, es, sock) and std.mem.eql(u8, en, proto.resolveName(name));
827 } 836 }
828 837