Skip to content

Run help50 COMMAND as though COMMAND were typed directly - #247

Merged
rongxin-liu merged 2 commits into
help50from
help50-passthrough
Sep 22, 2026
Merged

rongxin-liu merged 2 commits into
help50from
help50-passthrough

Conversation

@rongxin-liu

@rongxin-liu rongxin-liu commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Students (and existing docs and videos) know help50 make foo. On this branch that printed Usage: help50 [disable|enable|...] and exited 1, which in a codespace also triggered the duck nudge on help50's own usage text.

Now help50 COMMAND [ARGS...] runs COMMAND as though it had been typed directly, with the same output and exit status, and the prompt hook handles any failure exactly as it would have. So help50 make foo and make foo behave identically, and the old habit becomes harmless.

Two mechanisms, depending on where the command is run:

Within a help50 session (i.e., the shell help50 start wraps in script, where the prompt hook and helpers live), /etc/profile.d/help50.sh defines a help50 shell function. For anything other than the management subcommands, it evals the (%q-requoted) command line in the calling shell itself. Because it runs in the same process, aliases like rm -i and cd's HOME=$WORKDIR apply, shell functions are visible, and builtins that change the shell (cd, export) actually take effect: help50 cd foo changes directory. Errors are the interactive shell's own, with no stderr rewriting.

Outside a session (help50 disabled, bash --login -c, sudo), /opt/cs50/bin/help50 handles it:

  • exec "$@" for anything on PATH, so help50 make foo reaches the same make wrapper in /opt/cs50/bin as typing make foo.
  • Path-shaped names, builtins, and keywords (e.g., help50 ./foo.c, help50 cd nothere) go through bash -c, so bash itself reports Permission denied, Is a directory, or No such file or directory with the same exit status as a direct run; bash: line 1: is rewritten to bash: so the text matches what helpers expect, and the script waits for that rewrite to finish before exiting.
  • An unknown command fails with the shell's own bash: X: command not found (exit 127), so the bash helper's regexes match (help50 1s -> "Did you mean to run ls...").
  • Bare help50, -h, --help print a usage message explaining that help is now automatic (exit 0, so it doesn't trigger the duck).
  • disable|enable|is-enabled|start|status|stop are unchanged; only they require non-root, so sudo help50 echo x still runs echo x.

The prompt hook treats help50 COMMAND as COMMAND when deriving the command line it passes to helpers, so helpers that look at positional words (e.g., check 50 -> "Did you mean check50") and the "foo.c has changed, run make again" check still fire.

Known, not fixed: keywords whose spelling %q quotes (help50 [[ -f x ]]) fail as command not found.

Testing

tests/smoke.sh (make smoke) now covers the passthrough: exit-status propagation; exact stderr text and exit codes for unknown (1s), option-like (--version), builtin (cd nothere), and path-shaped (./foo.c, ./dir) commands; usage exiting 0; sudo help50 COMMAND working while sudo help50 is-enabled does not; the in-session function applying cd and aliases; and the hook reporting help50 ./slow as ./slow.

Under a pty in the built image, help50 ./foo.c, help50 1s, help50 cd nothere, help50 ./dir, help50 make foo, and help50 check 50 each produce the same advice as the direct command; help50 cd /tmp changes directory; help50 status still reports started.

Students used to run `help50 make foo`. Help now arrives automatically after
any failed command, so run COMMAND with the same exit status and let the prompt
hook handle the rest: help50 make foo behaves exactly like make foo. Builtins go
through bash -c with the shell's error wording preserved; an unknown command
fails with the shell's own "command not found" message so helpers match it.
Bare help50 prints usage (exit 0) explaining the new behaviour. The subcommands
(start/stop/...) are unchanged; only they require non-root.
@rongxin-liu rongxin-liu self-assigned this Sep 22, 2026
Within a help50 session, define help50 as a shell function that evals COMMAND
in the calling shell, so aliases (rm -i), functions, and cd behave exactly as
they would directly, and no stderr is rewritten through an unwaited sed.

In the script (outside a session), route path-shaped names (./foo.c, ./dir)
through bash -c so the shell's own Permission denied / Is a directory errors
reach the helpers instead of a synthesized "command not found"; wait for sed
before exiting; pass -- to type so option-like commands don't leak usage noise.

In the prompt hook, treat `help50 COMMAND` as COMMAND when deriving argv, so
helpers that look at positional words (e.g., `check 50`) and the ./ re-make
hint still fire.

Smoke-test the passthrough: exit statuses, exact error text for unknown,
option-like, builtin, and path-shaped commands, usage, sudo, the in-shell
function, and the hook's argv handling.
@rongxin-liu
rongxin-liu merged commit ba541a2 into help50 Sep 22, 2026
3 checks passed
@rongxin-liu
rongxin-liu deleted the help50-passthrough branch September 22, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant