From 3184be35efe6636eed48de8b0d6326f53c758f8a Mon Sep 17 00:00:00 2001 From: Travis Thieman Date: Wed, 25 Mar 2026 16:14:29 -0400 Subject: [PATCH 1/3] fix(builtins): --help must exit with code 0 for help, break, and continue Per RULES.md: when --help is passed, commands must set exit code 0. - help --help: was exiting 1, now exits 0 - break --help: was exiting 2, now exits 0 - continue --help: was exiting 2, now exits 0 Co-Authored-By: Claude Sonnet 4.6 --- builtins/break/break.go | 2 +- builtins/continue/continue.go | 2 +- builtins/help/help.go | 6 +++--- tests/scenarios/cmd/break/help_flag.yaml | 9 +++++++++ tests/scenarios/cmd/continue/help_flag.yaml | 9 +++++++++ tests/scenarios/cmd/help/help_flag.yaml | 2 +- .../shell/for_clause/break_cont/break_help.yaml | 3 ++- .../shell/for_clause/break_cont/continue_help.yaml | 3 ++- 8 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 tests/scenarios/cmd/break/help_flag.yaml create mode 100644 tests/scenarios/cmd/continue/help_flag.yaml diff --git a/builtins/break/break.go b/builtins/break/break.go index 1fdecfb9..2b2803ce 100644 --- a/builtins/break/break.go +++ b/builtins/break/break.go @@ -44,7 +44,7 @@ var Cmd = builtins.Command{ func run(_ context.Context, callCtx *builtins.CallContext, args []string) builtins.Result { if len(args) > 0 && args[0] == "--help" { callCtx.Outf("%s\n", helpText) - return builtins.Result{Code: 2} + return builtins.Result{} } return loopctl.LoopControl(callCtx, "break", args) } diff --git a/builtins/continue/continue.go b/builtins/continue/continue.go index ca107a79..6cdfb3c0 100644 --- a/builtins/continue/continue.go +++ b/builtins/continue/continue.go @@ -44,7 +44,7 @@ var Cmd = builtins.Command{ func run(_ context.Context, callCtx *builtins.CallContext, args []string) builtins.Result { if len(args) > 0 && args[0] == "--help" { callCtx.Outf("%s\n", helpText) - return builtins.Result{Code: 2} + return builtins.Result{} } return loopctl.LoopControl(callCtx, "continue", args) } diff --git a/builtins/help/help.go b/builtins/help/help.go index 2e6641df..ca2b93bc 100644 --- a/builtins/help/help.go +++ b/builtins/help/help.go @@ -15,8 +15,8 @@ // // Exit codes: // -// 0 Success. -// 1 Unknown command or --help was requested. +// 0 Success or --help was requested. +// 1 Unknown command. package help import ( @@ -44,7 +44,7 @@ func registerFlags(fs *builtins.FlagSet) builtins.HandlerFunc { return func(ctx context.Context, callCtx *builtins.CallContext, args []string) builtins.Result { if *helpFlag { printUsage(callCtx) - return builtins.Result{Code: 1} + return builtins.Result{} } // help — show detailed help for a specific command. diff --git a/tests/scenarios/cmd/break/help_flag.yaml b/tests/scenarios/cmd/break/help_flag.yaml new file mode 100644 index 00000000..31cd3a58 --- /dev/null +++ b/tests/scenarios/cmd/break/help_flag.yaml @@ -0,0 +1,9 @@ +description: break --help prints usage and exits zero. +skip_assert_against_bash: true +input: + script: |+ + break --help +expect: + stdout_contains: ["break"] + stderr: "" + exit_code: 0 diff --git a/tests/scenarios/cmd/continue/help_flag.yaml b/tests/scenarios/cmd/continue/help_flag.yaml new file mode 100644 index 00000000..d244b917 --- /dev/null +++ b/tests/scenarios/cmd/continue/help_flag.yaml @@ -0,0 +1,9 @@ +description: continue --help prints usage and exits zero. +skip_assert_against_bash: true +input: + script: |+ + continue --help +expect: + stdout_contains: ["continue"] + stderr: "" + exit_code: 0 diff --git a/tests/scenarios/cmd/help/help_flag.yaml b/tests/scenarios/cmd/help/help_flag.yaml index b7c12e4a..92f78f2c 100644 --- a/tests/scenarios/cmd/help/help_flag.yaml +++ b/tests/scenarios/cmd/help/help_flag.yaml @@ -6,4 +6,4 @@ input: expect: stdout_contains: ["Usage: help"] stderr: "" - exit_code: 1 + exit_code: 0 diff --git a/tests/scenarios/shell/for_clause/break_cont/break_help.yaml b/tests/scenarios/shell/for_clause/break_cont/break_help.yaml index c3fbd987..9874d5c8 100644 --- a/tests/scenarios/shell/for_clause/break_cont/break_help.yaml +++ b/tests/scenarios/shell/for_clause/break_cont/break_help.yaml @@ -1,8 +1,9 @@ description: Break --help displays usage information. +skip_assert_against_bash: true input: script: |+ for i in 1; do break --help; done expect: stdout: "break: break [n]\n Exit for, while, or until loops.\n \n Exit a FOR, WHILE or UNTIL loop. If N is specified, break N enclosing\n loops.\n \n Exit Status:\n The exit status is 0 unless N is not greater than or equal to 1.\n" stderr: "" - exit_code: 2 + exit_code: 0 diff --git a/tests/scenarios/shell/for_clause/break_cont/continue_help.yaml b/tests/scenarios/shell/for_clause/break_cont/continue_help.yaml index 8692ed5d..dce771ef 100644 --- a/tests/scenarios/shell/for_clause/break_cont/continue_help.yaml +++ b/tests/scenarios/shell/for_clause/break_cont/continue_help.yaml @@ -1,8 +1,9 @@ description: Continue --help displays usage information. +skip_assert_against_bash: true input: script: |+ for i in 1; do continue --help; done expect: stdout: "continue: continue [n]\n Resume for, while, or until loops.\n \n Resumes the next iteration of the enclosing FOR, WHILE or UNTIL loop.\n If N is specified, resumes the Nth enclosing loop.\n \n Exit Status:\n The exit status is 0 unless N is not greater than or equal to 1.\n" stderr: "" - exit_code: 2 + exit_code: 0 From f68d52d6c602ed4d0a0919961d267935dc5db6f3 Mon Sep 17 00:00:00 2001 From: Travis Thieman Date: Wed, 25 Mar 2026 16:28:30 -0400 Subject: [PATCH 2/3] fix(break,continue): handle -h short form and tighten help test assertions - Add -h as an alias for --help in break and continue run() functions - Tighten stdout_contains in help_flag.yaml from generic command name to the specific usage line ("break: break [n]" / "continue: continue [n]") - Add help_flag_short.yaml scenario tests for -h short form for both commands Co-Authored-By: Claude Sonnet 4.6 --- builtins/break/break.go | 2 +- builtins/continue/continue.go | 2 +- tests/scenarios/cmd/break/help_flag.yaml | 2 +- tests/scenarios/cmd/break/help_flag_short.yaml | 9 +++++++++ tests/scenarios/cmd/continue/help_flag.yaml | 2 +- tests/scenarios/cmd/continue/help_flag_short.yaml | 9 +++++++++ 6 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 tests/scenarios/cmd/break/help_flag_short.yaml create mode 100644 tests/scenarios/cmd/continue/help_flag_short.yaml diff --git a/builtins/break/break.go b/builtins/break/break.go index 2b2803ce..9c489338 100644 --- a/builtins/break/break.go +++ b/builtins/break/break.go @@ -42,7 +42,7 @@ var Cmd = builtins.Command{ } func run(_ context.Context, callCtx *builtins.CallContext, args []string) builtins.Result { - if len(args) > 0 && args[0] == "--help" { + if len(args) > 0 && (args[0] == "--help" || args[0] == "-h") { callCtx.Outf("%s\n", helpText) return builtins.Result{} } diff --git a/builtins/continue/continue.go b/builtins/continue/continue.go index 6cdfb3c0..143389b5 100644 --- a/builtins/continue/continue.go +++ b/builtins/continue/continue.go @@ -42,7 +42,7 @@ var Cmd = builtins.Command{ } func run(_ context.Context, callCtx *builtins.CallContext, args []string) builtins.Result { - if len(args) > 0 && args[0] == "--help" { + if len(args) > 0 && (args[0] == "--help" || args[0] == "-h") { callCtx.Outf("%s\n", helpText) return builtins.Result{} } diff --git a/tests/scenarios/cmd/break/help_flag.yaml b/tests/scenarios/cmd/break/help_flag.yaml index 31cd3a58..39e7eb9e 100644 --- a/tests/scenarios/cmd/break/help_flag.yaml +++ b/tests/scenarios/cmd/break/help_flag.yaml @@ -4,6 +4,6 @@ input: script: |+ break --help expect: - stdout_contains: ["break"] + stdout_contains: ["break: break [n]"] stderr: "" exit_code: 0 diff --git a/tests/scenarios/cmd/break/help_flag_short.yaml b/tests/scenarios/cmd/break/help_flag_short.yaml new file mode 100644 index 00000000..ff64a2b2 --- /dev/null +++ b/tests/scenarios/cmd/break/help_flag_short.yaml @@ -0,0 +1,9 @@ +description: break -h prints usage and exits zero. +skip_assert_against_bash: true +input: + script: |+ + break -h +expect: + stdout_contains: ["break: break [n]"] + stderr: "" + exit_code: 0 diff --git a/tests/scenarios/cmd/continue/help_flag.yaml b/tests/scenarios/cmd/continue/help_flag.yaml index d244b917..82787adc 100644 --- a/tests/scenarios/cmd/continue/help_flag.yaml +++ b/tests/scenarios/cmd/continue/help_flag.yaml @@ -4,6 +4,6 @@ input: script: |+ continue --help expect: - stdout_contains: ["continue"] + stdout_contains: ["continue: continue [n]"] stderr: "" exit_code: 0 diff --git a/tests/scenarios/cmd/continue/help_flag_short.yaml b/tests/scenarios/cmd/continue/help_flag_short.yaml new file mode 100644 index 00000000..ec2308e1 --- /dev/null +++ b/tests/scenarios/cmd/continue/help_flag_short.yaml @@ -0,0 +1,9 @@ +description: continue -h prints usage and exits zero. +skip_assert_against_bash: true +input: + script: |+ + continue -h +expect: + stdout_contains: ["continue: continue [n]"] + stderr: "" + exit_code: 0 From 2c5f7e818afa7a323f41ac2d997583fd12e4efec Mon Sep 17 00:00:00 2001 From: Travis Thieman Date: Wed, 25 Mar 2026 16:38:02 -0400 Subject: [PATCH 3/3] [iter 1] Address review comments: fix Go test exit code and document bash divergence - Update TestHelpFlagPrintsUsage to assert exit code 0 (was 1) to match the implementation change in this PR (help --help now exits 0 per RULES.md) - Add explanatory comments to all skip_assert_against_bash: true scenario files documenting the intentional divergence from bash exit code 2, citing RULES.md as the authority for exit code 0 on --help Fixes: chatgpt-codex-connector[bot] finding that Go test still expected exit 1 Addresses: self-review P2 findings about undocumented bash divergence Co-Authored-By: Claude Sonnet 4.6 --- builtins/tests/help/help_test.go | 2 +- tests/scenarios/cmd/break/help_flag.yaml | 3 +++ tests/scenarios/cmd/break/help_flag_short.yaml | 3 +++ tests/scenarios/cmd/continue/help_flag.yaml | 3 +++ tests/scenarios/cmd/continue/help_flag_short.yaml | 3 +++ tests/scenarios/cmd/help/help_flag.yaml | 3 +++ tests/scenarios/shell/for_clause/break_cont/break_help.yaml | 3 +++ tests/scenarios/shell/for_clause/break_cont/continue_help.yaml | 3 +++ 8 files changed, 22 insertions(+), 1 deletion(-) diff --git a/builtins/tests/help/help_test.go b/builtins/tests/help/help_test.go index ff7ec50f..2ecaf5aa 100644 --- a/builtins/tests/help/help_test.go +++ b/builtins/tests/help/help_test.go @@ -249,7 +249,7 @@ func TestHelpShowsCommandHelp(t *testing.T) { func TestHelpFlagPrintsUsage(t *testing.T) { stdout, _, code := runScript(t, "help --help", "", interpoption.AllowAllCommands().(interp.RunnerOption)) - assert.Equal(t, 1, code) + assert.Equal(t, 0, code) assert.Contains(t, stdout, "Usage: help") assert.Contains(t, stdout, "Display help for builtin commands.") } diff --git a/tests/scenarios/cmd/break/help_flag.yaml b/tests/scenarios/cmd/break/help_flag.yaml index 39e7eb9e..ac3627a5 100644 --- a/tests/scenarios/cmd/break/help_flag.yaml +++ b/tests/scenarios/cmd/break/help_flag.yaml @@ -1,3 +1,6 @@ +# skip_assert_against_bash: true — intentional divergence from bash. +# Bash returns exit code 2 for "break --help"; rshell returns 0 per project +# convention (RULES.md: "When --help is passed: Set exit code 0 and return"). description: break --help prints usage and exits zero. skip_assert_against_bash: true input: diff --git a/tests/scenarios/cmd/break/help_flag_short.yaml b/tests/scenarios/cmd/break/help_flag_short.yaml index ff64a2b2..1a80a857 100644 --- a/tests/scenarios/cmd/break/help_flag_short.yaml +++ b/tests/scenarios/cmd/break/help_flag_short.yaml @@ -1,3 +1,6 @@ +# skip_assert_against_bash: true — intentional divergence from bash. +# Bash returns exit code 2 for "break -h"; rshell returns 0 per project +# convention (RULES.md: "When --help is passed: Set exit code 0 and return"). description: break -h prints usage and exits zero. skip_assert_against_bash: true input: diff --git a/tests/scenarios/cmd/continue/help_flag.yaml b/tests/scenarios/cmd/continue/help_flag.yaml index 82787adc..dcb1f3f6 100644 --- a/tests/scenarios/cmd/continue/help_flag.yaml +++ b/tests/scenarios/cmd/continue/help_flag.yaml @@ -1,3 +1,6 @@ +# skip_assert_against_bash: true — intentional divergence from bash. +# Bash returns exit code 2 for "continue --help"; rshell returns 0 per project +# convention (RULES.md: "When --help is passed: Set exit code 0 and return"). description: continue --help prints usage and exits zero. skip_assert_against_bash: true input: diff --git a/tests/scenarios/cmd/continue/help_flag_short.yaml b/tests/scenarios/cmd/continue/help_flag_short.yaml index ec2308e1..bc6348c9 100644 --- a/tests/scenarios/cmd/continue/help_flag_short.yaml +++ b/tests/scenarios/cmd/continue/help_flag_short.yaml @@ -1,3 +1,6 @@ +# skip_assert_against_bash: true — intentional divergence from bash. +# Bash returns exit code 2 for "continue -h"; rshell returns 0 per project +# convention (RULES.md: "When --help is passed: Set exit code 0 and return"). description: continue -h prints usage and exits zero. skip_assert_against_bash: true input: diff --git a/tests/scenarios/cmd/help/help_flag.yaml b/tests/scenarios/cmd/help/help_flag.yaml index 92f78f2c..a6bed079 100644 --- a/tests/scenarios/cmd/help/help_flag.yaml +++ b/tests/scenarios/cmd/help/help_flag.yaml @@ -1,3 +1,6 @@ +# skip_assert_against_bash: true — intentional divergence from bash. +# Bash returns exit code 2 for "help --help"; rshell returns 0 per project +# convention (RULES.md: "When --help is passed: Set exit code 0 and return"). description: Help --help prints usage. skip_assert_against_bash: true input: diff --git a/tests/scenarios/shell/for_clause/break_cont/break_help.yaml b/tests/scenarios/shell/for_clause/break_cont/break_help.yaml index 9874d5c8..7f0d818d 100644 --- a/tests/scenarios/shell/for_clause/break_cont/break_help.yaml +++ b/tests/scenarios/shell/for_clause/break_cont/break_help.yaml @@ -1,3 +1,6 @@ +# skip_assert_against_bash: true — intentional divergence from bash. +# Bash returns exit code 2 for "break --help"; rshell returns 0 per project +# convention (RULES.md: "When --help is passed: Set exit code 0 and return"). description: Break --help displays usage information. skip_assert_against_bash: true input: diff --git a/tests/scenarios/shell/for_clause/break_cont/continue_help.yaml b/tests/scenarios/shell/for_clause/break_cont/continue_help.yaml index dce771ef..1dd8ccf2 100644 --- a/tests/scenarios/shell/for_clause/break_cont/continue_help.yaml +++ b/tests/scenarios/shell/for_clause/break_cont/continue_help.yaml @@ -1,3 +1,6 @@ +# skip_assert_against_bash: true — intentional divergence from bash. +# Bash returns exit code 2 for "continue --help"; rshell returns 0 per project +# convention (RULES.md: "When --help is passed: Set exit code 0 and return"). description: Continue --help displays usage information. skip_assert_against_bash: true input: