@docs/RULES.md
This is a minimal bash/POSIX like shell interpreter. Safety is the primary goal.
This shell is intended to be used by AI Agents.
The shell is supported on Linux, Windows and macOS.
README.mdandSHELL_FEATURES.mdmust be kept up to date with the implementation.- When adding or modifying a builtin, set or update the
Descriptionfield on itsbuiltins.Commandstruct and verify the command appears correctly inhelpoutput.
- IMPORTANT: Always run
make fmtafter making any edits. This is a mandatory step — no exceptions. CI will reject unformatted code. Run it after every change, before committing, and before running tests. Do not skip this step. - All Go files must be formatted with
gofmtbefore committing.make fmthandles this automatically. You can verify withgofmt -l .(no output means clean).
- Always open pull requests in draft mode. Use
gh pr create --draft(or the GitHub UI's "Draft pull request" option). Only mark a PR ready for review once all CI checks pass and the work is complete. - Never add the
verified/allowed_symbolsGitHub label. This label is reserved for human manual approval only. Don't try to fix CI failures related to this.
ssandip routebypassAllowedPathsfor/proc/net/*reads. Both builtins delegate/proc/net/I/O to internal packages (builtins/internal/procnetsocketforss,builtins/internal/procnetrouteforip route) that callos.Opendirectly on kernel pseudo-filesystem paths (e.g./proc/net/tcp,/proc/net/route). These paths are hardcoded in the implementation and are never derived from user input, soAllowedPathsrestrictions do not apply to them. As a consequence, operators cannot useAllowedPathsto blockssfrom enumerating local sockets orip routefrom reading the routing table. This is an intentional trade-off: the paths are non-user-controllable, so there is no sandbox-escape risk, but the operator loses the ability to deny these reads via sandbox configuration.
- ALWAYS prioritise fixing the shell implementation to match bash behaviour over changing tests to match the current (incorrect) shell output. Never "fix" a failing test by updating its expected output to match broken shell behaviour — fix the shell instead.
- Only deviate from bash behaviour when the shell is intentionally different (e.g. sandbox restrictions, blocked commands, readonly enforcement).
-
Before submitting any change that touches
tests/scenarios/or builtin implementations, run the bash comparison tests locally. These are skipped by default and require Docker:RSHELL_BASH_TEST=1 go test ./tests/ -run TestShellScenariosAgainstBash -timeout 120sThe test suite runs all scenarios against
debian:bookworm-slim(GNU bash + GNU coreutils) and compares output byte-for-byte. Only setskip_assert_against_bash: truein a scenario when the behavior intentionally diverges from bash (e.g. sandbox restrictions, blocked commands). -
Prefer scenario tests (
tests/scenarios/) over Go tests. Scenario tests are declarative YAML files that are automatically validated against both the shell and bash, making them easier to write, review, and maintain. Only use Go tests when scenario tests cannot express the required behaviour (e.g. testing Go APIs directly, complex programmatic assertions). -
ip route show/ip route gethappy-path scenario tests cannot be added. The scenario test framework has no platform-skip mechanism, andip routereads/proc/net/routewhich is Linux-only — the command exits 1 with "not supported" on macOS and Windows. Happy-path coverage lives inbuiltins/tests/ip/ip_linux_test.goinstead. -
In test scenarios, use
expect.stderrwhen possible instead ofstderr_contains. -
Always use the YAML
|+block scalar forinput.script,expect.stdout, andexpect.stderrvalues, even single-line ones. -
Test scenarios are asserted against bash by default. Only set
skip_assert_against_bash: truefor features that intentionally diverge from standard bash behavior (e.g. blocked commands, restricted redirects, readonly enforcement). -
When expected output differs on Windows (e.g. path separators
\vs/), use Windows-specific assertion fields:stdout_windows/stderr_windows— overridestdout/stderron Windows.stdout_contains_windows/stderr_contains_windows— overridestdout_contains/stderr_containson Windows.- If the Windows field is not set, the non-Windows field is used as fallback.