summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 02:23:59 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commit45a66bd1c062a5cf4d63b1df68e050b059cdfa1d (patch)
tree3c6ecbd21dd2d07c624b8799defc31d94d51a256
parent90f5c414a90afec978c8c0add58fd2a7e29acbaa (diff)
downloadpardes-45a66bd1c062a5cf4d63b1df68e050b059cdfa1d.tar.gz
pardes-45a66bd1c062a5cf4d63b1df68e050b059cdfa1d.zip
The default Shell is $SHELL, else /bin/sh
With no Shell set, pardes ran fish, which a box may not have and a user may not use. The default is now the user's login shell ($SHELL), else /bin/sh; an explicit Shell still overrides it. The tests pin their own shell (snapshots bash, the python suites /bin/sh or bash) and pass with SHELL unset; the Config report and docs say where the default comes from. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--.agents/skills/pardes-9p/SKILL.md2
-rw-r--r--docs/config.md3
-rw-r--r--docs/fs.md2
-rw-r--r--src/Output.zig2
-rw-r--r--src/builtins.zig2
-rw-r--r--src/config.zig14
-rw-r--r--src/ninep/ctl.zig2
-rw-r--r--src/pardes.zig2
-rw-r--r--test/macos_e2e.swift2
-rw-r--r--test/output.zig3
-rw-r--r--test/snapshot.zig5
-rw-r--r--test/snapshots/shellset.snap4
12 files changed, 27 insertions, 16 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md
index 61b117a5..0cd52715 100644
--- a/.agents/skills/pardes-9p/SKILL.md
+++ b/.agents/skills/pardes-9p/SKILL.md
@@ -149,7 +149,7 @@ the editor, not as a write error, so inspect the resulting pane, index, message
or screen; only a malformed line fails the write itself. A word no builtin
knows (a typo included) is a command line: written at a terminal at its
prompt it is typed into that shell; from anywhere else it runs as a command
-pane, a terminal whose child is the root ctl's `Shell` (fish unless set)
+pane, a terminal whose child is the root ctl's `Shell` ($SHELL, else /bin/sh, unless set)
running `-c` the line in the pane's directory, which ends showing `exit N` (a typo: `exit 127`) and logs `run
<serial> <line>` and `exit <serial> <N|?>` -- `exec` reads back its serial,
so follow `log` for the exit. The directory's next command reuses a finished
diff --git a/docs/config.md b/docs/config.md
index 40d0fb24..93190556 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -46,7 +46,8 @@ the browser still applies the compiled percentage to its DOM glyphs.
The startup path is printed whether or not a file exists — that is usually when
it is most useful — and is ordinary selectable text, so a right click on it
opens the file. Shell follows the same requested/effective/pending model as
-Font. `Compiled default shell` is the command built into the binary; `Shell
+Font. `Default shell` is the one used while no `Shell` is set: `$SHELL`, the
+user's login shell, else `/bin/sh`; an explicit `Shell` overrides it. `Shell
effective (last spawn)` is the executable the native host really chose after
installation lookup and fallback. A changed request remains pending until a
terminal is spawned, because the core does not resolve native executables.
diff --git a/docs/fs.md b/docs/fs.md
index a93137bb..95ab4de0 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -304,7 +304,7 @@ same tree without leaving the process.
is typed into that shell (and a terminal whose shell exits, `exit` typed
or run, closes its pane). From anywhere else -- a file, a scratch, a tag,
a terminal whose tty a program holds -- it runs as a command pane: a
- terminal whose child is the root ctl's `Shell` (fish unless set) run
+ terminal whose child is the root ctl's `Shell` ($SHELL, else /bin/sh, unless set) run
with `-c` and the line, in the pane's directory, with job control on
(bash, sh, dash, zsh, ksh `-m`; fish `status job-control full`),
which shows its output and then `exit N` (its tag reads `<dir> (<line>)
diff --git a/src/Output.zig b/src/Output.zig
index 2f462fd1..ee932768 100644
--- a/src/Output.zig
+++ b/src/Output.zig
@@ -665,7 +665,7 @@ pub fn openConfig(p: *Pardes, id: usize) !void {
.startup_config_path = p.opts.startup_config_path,
.platform = @tagName(pardes.platform),
.theme_name = p.theme().name,
- .compiled_default_shell = config.default_shell,
+ .compiled_default_shell = config.defaultShell(),
.gui_shader_source_mode = if (gui_shader_source_mode) |mode|
mode.label()
else
diff --git a/src/builtins.zig b/src/builtins.zig
index 8c6228c0..f7104a8c 100644
--- a/src/builtins.zig
+++ b/src/builtins.zig
@@ -399,7 +399,7 @@ test "Ligatures is a setting only in the shell that shapes text" {
.startup_config_path = null,
.platform = @tagName(pardes.platform),
.theme_name = p.theme().name,
- .compiled_default_shell = config.default_shell,
+ .compiled_default_shell = config.defaultShell(),
.hover_delay_frames = null,
.native_images = false,
.capabilities = capabilities,
diff --git a/src/config.zig b/src/config.zig
index fb38e4dd..a59fe18d 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -345,7 +345,15 @@ test "wheel drift guard" {
try std.testing.expect(wheelTick(&g, false));
}
-pub const default_shell = "fish";
+/// The shell when no `Shell` is set: the user's login shell ($SHELL), as a
+/// terminal would start, else /bin/sh. An explicit `Shell` overrides it.
+pub fn defaultShell() []const u8 {
+ if (comptime pardes.hosted) if (std.c.getenv("SHELL")) |s| {
+ const shell = std.mem.span(s);
+ if (shell.len > 0) return shell;
+ };
+ return "/bin/sh";
+}
// Minimum file gutter width, including the space after the line number.
pub const PREFIX_W: u16 = 5;
@@ -1364,7 +1372,7 @@ pub const Runtime = struct {
try out.writeAll("Startup config: no per-user config path\n");
try out.print(
"Platform: {s}\n" ++
- "Compiled default shell: {s}\n",
+ "Default shell ($SHELL, else /bin/sh): {s}\n",
.{ context.platform, context.compiled_default_shell },
);
if (context.gui_shader_source_mode) |mode|
@@ -1719,7 +1727,7 @@ pub const Runtime = struct {
"TaglineSize 82\n",
"Startup config: /tmp/pardes/init\n",
"Platform: gui\n",
- "Compiled default shell: /bin/sh\n",
+ "Default shell ($SHELL, else /bin/sh): /bin/sh\n",
"GUI shader source: live GLSL compiled during this build\n",
"Look hover delay: 18 frames\n",
"Native images: on\n",
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 8a35c381..0ab63fbb 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -399,7 +399,7 @@ pub fn writeSettingsOf(p: *Pardes, s: *const config.Runtime, w: *std.Io.Writer)
.theme => try w.print("{s} {s}\n", .{ word, if (s == &p.settings) p.theme().name else pardes.themes[s.theme].name }),
.shell => {
const chosen = s.shell.requested.get();
- try w.print("{s} {s}\n", .{ word, if (chosen.len > 0) chosen else config.default_shell });
+ try w.print("{s} {s}\n", .{ word, if (chosen.len > 0) chosen else config.defaultShell() });
},
.font => {
const requested = s.font.requested_name.get();
diff --git a/src/pardes.zig b/src/pardes.zig
index a0b11566..f2791e33 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -4925,7 +4925,7 @@ pub const Pardes = struct {
pub fn shellBin(p: *const Pardes) []const u8 {
const selected = p.settings.shell.requested.get();
- return if (selected.len == 0) config.default_shell else selected;
+ return if (selected.len == 0) config.defaultShell() else selected;
}
pub fn acknowledgeShell(p: *Pardes, id: usize, executable: []const u8, prompt_marks: bool) void {
diff --git a/test/macos_e2e.swift b/test/macos_e2e.swift
index 5f172f9d..2b0eedad 100644
--- a/test/macos_e2e.swift
+++ b/test/macos_e2e.swift
@@ -1251,7 +1251,7 @@ private func buildWorld(stem: String) throws -> String {
// sources $HOME/.bashrc (host_io.Shell) — so this is what pins the
// prompt to `$ ` and keeps the developer's history out of the capture.
try "PS1='$ '\nHISTFILE=\n".write(toFile: "\(home)/.bashrc", atomically: true, encoding: .utf8)
- // ...and the config is not empty, because the DEFAULT shell is fish, whose
+ // ...and the config is not empty, because the DEFAULT shell is $SHELL (fish, say), whose
// prompt carries a hostname and whose greeting carries a version. A golden
// taken against that is a golden for one machine.
try "Shell bash\n".write(toFile: "\(pardesConfig)/init", atomically: true, encoding: .utf8)
diff --git a/test/output.zig b/test/output.zig
index 419bcca2..f3e61e93 100644
--- a/test/output.zig
+++ b/test/output.zig
@@ -544,10 +544,11 @@ test "Config prints the startup path and refreshes its one output" {
for ([_][]const u8{
"Startup config: " ++ path ++ "\n",
"Theme orchard\n",
- "Shell requested (new panes) " ++ config.default_shell ++ " (default)\n",
"Shell effective (last spawn) (none)\n",
"Shell pending on\n",
}) |line| try std.testing.expect(std.mem.indexOf(u8, out.content, line) != null);
+ var default_line: [256]u8 = undefined;
+ try std.testing.expect(std.mem.indexOf(u8, out.content, try std.fmt.bufPrint(&default_line, "Shell requested (new panes) {s} (default)\n", .{config.defaultShell()})) != null);
try std.testing.expectEqualStrings(config.config_buffer, std.fs.path.basename(out.path));
try std.testing.expectEqual(panes.Output.Origin{ .cmd = .Config }, out.output.?.from);
diff --git a/test/snapshot.zig b/test/snapshot.zig
index 2fa8e89c..c49e7645 100644
--- a/test/snapshot.zig
+++ b/test/snapshot.zig
@@ -562,8 +562,9 @@ fn runScript(arena: std.mem.Allocator, exe_z: [:0]const u8, script_path: []const
// XDG directory that points outside this per-snapshot fake home.
const config_home = try std.fmt.allocPrintSentinel(arena, "{s}/.config", .{home}, 0);
_ = setenv("XDG_CONFIG_HOME", config_home, 1);
- // ...and it is not empty: the DEFAULT shell is fish, whose prompt carries
- // a hostname and whose greeting carries a version, so a golden taken
+ // ...and it is not empty: the DEFAULT shell is $SHELL, whatever this
+ // machine's user logs in with (fish's prompt carries a hostname and its
+ // greeting a version), so a golden taken
// against it would be a golden for this machine. Every script here runs
// bash — the .bashrc above pins its prompt to `$ ` — and the fish path is
// covered by host_io.Shell's unit tests instead. This is an ordinary
diff --git a/test/snapshots/shellset.snap b/test/snapshots/shellset.snap
index d2bacf38..ef8b02b9 100644
--- a/test/snapshots/shellset.snap
+++ b/test/snapshots/shellset.snap
@@ -2,8 +2,8 @@
# here is the case that has to hold on a machine you did not set up: the name
# resolves to nothing, and the pane opens anyway.
#
-# That is not a corner — it is the DEFAULT's failure mode. pardes ships
-# `Shell fish` as the default, and a box without fish must still get a working
+# That is not a corner — it is the DEFAULT's failure mode. pardes defaults
+# to $SHELL, which may name a shell the box lacks, and it must still get a working
# shell rather than a pane whose child dies at exec and shows one EOF. So the
# lookup falls back, and this is the proof: a name nothing resolves to, and the
# new pane still greets and lists like any other.