A virtual jailed shell environment for Go apps backed by an io/fs#FS.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs(just-bash-port): document ec.Runner subshell pattern

Add reference material for porting commands that re-dispatch a child
argv (time, nice, xargs) through the shell so inner calls hit the same
exec-handler chain as a top-level invocation. Covers shell-quoting,
parsing, subshell dispatch, the nil-Runner guard, and how to recognize
registry.ErrCommandNotFound from the inner exit.

Signed-off-by: Xe Iaso <me@xeiaso.net>
Assisted-by: Claude Opus 4.7 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>

Xe Iaso 2ec17e0f 1ea1e5ed

+66 -1
+3 -1
.claude/skills/just-bash-port/SKILL.md
··· 29 29 30 30 - [references/execer-surface.md](references/execer-surface.md) — `command.Execer` 31 31 interface, `ExecContext` fields, exit-code conventions, the billy filesystem, 32 - and the path-resolution helper used across kefka commands. 32 + the path-resolution helper used across kefka commands, and the 33 + `ec.Runner` subshell pattern for commands that re-dispatch a child 34 + argv (e.g. `time`, `nice`, `xargs`). 33 35 - [references/getopt-v2.md](references/getopt-v2.md) — how to wire 34 36 `pborman/getopt/v2`, how to set the usage line, how to expose `--help`, 35 37 and how to map TypeScript `parseArgs` flag definitions onto getopt
+63
.claude/skills/just-bash-port/references/execer-surface.md
··· 15 15 Dir string // fsys-relative pwd 16 16 Environ expand.Environ // mvdan.cc/sh/v3/expand 17 17 FS billy.Filesystem 18 + Runner *interp.Runner // active shell runner; may be nil 18 19 } 19 20 20 21 type Execer interface { ··· 25 26 `args` is **already stripped of `argv[0]`** — the registry has split 26 27 the command name off before calling `Exec`. Never assume `args[0]` is 27 28 the command name. 29 + 30 + ## ec.Runner — re-entering the shell 31 + 32 + Most ports never touch `ec.Runner`. It exists for the rare command 33 + that needs to dispatch a *child argv* through the shell itself — 34 + `time CMD`, `nice CMD`, `xargs CMD ...` — so the inner call goes 35 + through the same exec-handler chain (registered builtins, shell 36 + functions, PATH binaries) that the user would have hit by typing CMD 37 + directly. 38 + 39 + If you're porting one of those: shell-quote the inner argv, parse it 40 + as bash, run it through a subshell, and propagate the exit. Pattern 41 + (see `command/internal/time/time.go` for the live version): 42 + 43 + ```go 44 + import ( 45 + "mvdan.cc/sh/v3/interp" 46 + "mvdan.cc/sh/v3/syntax" 47 + ) 48 + 49 + if ec.Runner == nil { 50 + fmt.Fprint(ec.Stderr, "<name>: exec not available\n") 51 + return interp.ExitStatus(127) 52 + } 53 + 54 + var b strings.Builder 55 + for i, a := range innerArgs { 56 + if i > 0 { 57 + b.WriteByte(' ') 58 + } 59 + q, err := syntax.Quote(a, syntax.LangBash) 60 + if err != nil { 61 + return interp.ExitStatus(1) 62 + } 63 + b.WriteString(q) 64 + } 65 + prog, err := syntax.NewParser(syntax.Variant(syntax.LangBash)). 66 + Parse(strings.NewReader(b.String()), "<<name>>") 67 + if err != nil { 68 + return interp.ExitStatus(1) 69 + } 70 + sub := ec.Runner.Subshell() 71 + interp.StdIO(ec.Stdin, ec.Stdout, ec.Stderr)(sub) 72 + return sub.Run(ctx, prog) 73 + ``` 74 + 75 + Notes: 76 + 77 + - **Do not** stash the registry on your `Impl` and call `reg.Get` 78 + yourself. That bypasses shell functions and the call/exec handler 79 + chain. 80 + - Tests for runner-using commands need a real `*interp.Runner` whose 81 + `ExecHandler` dispatches into a `registry.Impl`. See 82 + `command/internal/time/time_test.go` (`newRunner` helper) for the 83 + pattern. 84 + - `ec.Runner` may be `nil` (embedders or older test harnesses). Guard 85 + with a `127 + "exec not available"` short-circuit, matching `time`. 86 + - Inside the subshell, the registry's "command not found" surfaces 87 + as `kefka: command not found: <NAME>` plus 88 + `errors.Join(interp.ExitStatus(127), registry.ErrCommandNotFound)` 89 + — match against `registry.ErrCommandNotFound` if you need to 90 + distinguish it from other 127s. 28 91 29 92 ## Filesystem 30 93