Use the user's login shell, and fix login-shell argv

Two separate problems made the terminal run the wrong shell:

Pty.create took a single argv slice and used argv[0] as both the exec path
and the argument vector's first entry. Session passed {shell, "-shell"},
so the shell received "-zsh" as an ordinary argument rather than as its
argv[0]. Bash quietly tolerated it; zsh rejects it and exits immediately,
which closed the tab and took the window down with it. The exec path and
argv are now separate parameters.

Shell resolution preferred $SHELL, which describes whichever shell launched
us rather than the one the user configured. Inside 'nix develop' that is
Nix's own minimal bash, so the terminal never opened the user's zsh. We now
read the login shell from the passwd database and keep $SHELL only as a
fallback for systems without a usable passwd entry.

shot.sh no longer forces a shell, so screenshots reflect real behavior.
This commit is contained in:
Greyson Parrelli
2026-08-11 09:11:53 -04:00
parent 738cead680
commit 7af5cf72e6
4 changed files with 60 additions and 14 deletions
+15 -3
View File
@@ -96,13 +96,14 @@ pub fn create(
const shell = try defaultShell(alloc);
defer alloc.free(shell);
// A leading '-' in argv[0] tells the shell to start as a login shell.
// A leading '-' in argv[0] tells the shell to start as a login shell,
// so it reads the user's profile.
const argv0 = try std.fmt.allocPrintSentinel(alloc, "-{s}", .{
std.fs.path.basename(shell),
}, 0);
defer alloc.free(argv0);
self.pty = try .create(alloc, &.{ shell, argv0 }, .{
self.pty = try .create(alloc, shell, &.{argv0}, .{
.ws_row = rows,
.ws_col = cols,
});
@@ -126,12 +127,23 @@ pub fn destroy(self: *Session) void {
self.alloc.destroy(self);
}
/// Resolve the user's shell, falling back to something that always exists.
/// Resolve the shell to run, most authoritative source first.
///
/// The passwd entry comes first deliberately. $SHELL is only a description of
/// the shell that happened to launch us, and plenty of environments overwrite
/// it: `nix develop` replaces it with its own bash, and Flatpak pins it to
/// /bin/sh. The passwd entry is what the user actually configured, so it is
/// the better answer to "the user's default shell". $SHELL remains a fallback
/// for systems with no usable passwd entry, such as minimal containers.
fn defaultShell(alloc: std.mem.Allocator) ![:0]const u8 {
var buf: [std.fs.max_path_bytes]u8 = undefined;
if (Pty.loginShell(&buf)) |shell| return alloc.dupeZ(u8, shell);
if (std.c.getenv("SHELL")) |sh| {
const span = std.mem.span(sh);
if (span.len > 0) return alloc.dupeZ(u8, span);
}
return alloc.dupeZ(u8, "/bin/sh");
}