Skip to content

Keep the end of long output, and pass the command line to _helpless - #246

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

rongxin-liu merged 2 commits into
help50from
help50-payload

Conversation

@rongxin-liu

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

Copy link
Copy Markdown
Contributor

Two improvements to what reaches the CS50 Duck when no helper matches, from testing on the canary codespace (cs50/codespace#196).

Keep the end of long output. The typescript was capped with head -n 1024, i.e. the start of the output. Errors are usually at the end: tracebacks, make: *** Error, segfaults. A program that printed a few thousand lines and then crashed lost its error entirely, so neither the helpers nor _helpless ever saw it. Now the first 64 lines are kept (the echoed command line lives there and is still found) plus the last 1024, with a [... N lines omitted ...] marker between them. Short output is unchanged.

Pass the command line to _helpless. cs50.ai's /api/v1/help wraps whatever it receives in a code block and asks the model to explain it, but until now it only got the output, not what the student typed. _helpless now receives the command line as a second argument (_helpless OUTPUT CMD). The default _helpless ignores it; output stays $1, so cs50/codespace's "empty output, nothing to explain" check is unaffected. The companion change that uses it is cs50/codespace#199.

The hook contract is now documented above the default definitions in help50.sh.

Testing. tests/smoke.sh now drives _help50 directly in the image with a fabricated typescript and a fake _helpless: a command that prints 3000 lines and then fails yields ./slow as the second argument and, as the first, output that starts at the program's first line, contains [... 1914 lines omitted ...], no longer contains the middle, and ends with the error; a failed command with no output yields an empty first argument. The check fails against the previous help50.sh. Also verified under a pty on the canary codespace: cat nothere yields $ cat nothere + its error in the codespace payload.

The typescript was capped with head -n 1024, keeping the start of the output.
Errors are usually at the end (tracebacks, make: *** Error, segfaults), so a
program that printed a lot and then failed lost its error before any helper or
_helpless saw it. Now keep the first 64 lines (where the command line is echoed
and found) plus the last 1024, with a marker for what was omitted.

_helpless now also receives the command line as a second argument, so whatever
explains the output (in cs50/codespace, the CS50 Duck via cs50.ai) can see what
was run. The default _helpless ignores it; output stays the first argument, so
the codespace's empty-output check is unaffected.
Nothing exercised _help50 itself: the head/tail cap and the new command-line
argument were only checked by hand under a pty. Drive _help50 directly in the
image instead, with a fabricated typescript and a fake _helpless, and assert
that a command printing 3000 lines then an error yields the command line as
the second argument and, as the first, output that starts at the program's
first line, carries the omission marker, drops the middle, and ends with the
error. Also check that a failed command with no output yields an empty first
argument, which is what the codespace's empty-output check depends on.

The check fails against the previous help50.sh.
@rongxin-liu rongxin-liu self-assigned this Sep 22, 2026
@rongxin-liu
rongxin-liu merged commit dd74ee9 into help50 Sep 22, 2026
3 checks passed
@rongxin-liu
rongxin-liu deleted the help50-payload branch September 22, 2026 05:50
hgarfer pushed a commit to hgarfer/codespace that referenced this pull request Sep 30, 2026
cs50/cli#246 passes the failed command's line to _helpless as a second
argument. Include it, as a transcript ($ cmd, then output), so cs50.ai can
see what the student ran, not just what came back.
rongxin-liu added a commit that referenced this pull request Sep 30, 2026
Reimplements help50 in Bash, running locally and automatically per login
shell, without a server. Usage is inspired by systemctl:

- help50 start/stop/status/enable/disable/is-enabled control a session
  that logs the shell's I/O via script to /tmp/help50.$PPID
- help50 COMMAND [ARGS...] runs COMMAND as though typed directly, with
  the same exit status
- HELP50_DISABLED in the environment is a kill switch, so that as a
  Codespaces secret help50 can be turned off fleet-wide without a rebuild

/etc/profile.d/help50.sh installs a PROMPT_COMMAND hook that, after a
failed command, strips the typescript of ANSI/control characters and
terminal echo, bounds the read (first 64K + last 1M) and the output
(first 64 + last 1,024 lines), and passes it to each executable helper in
/opt/cs50/lib/help50/ under a 5-second timeout. Helper output is shown via
_helpful; otherwise _helpless receives the output and command line (a
no-op here, overridden in cs50/codespace to relay to the CS50 Duck).

Also adds /opt/cs50/lib/cli helper functions (_alert, _ansi, _find,
_fold, _sure) used by the make, sqlite3, http-server, and valgrind
wrappers; helpers for bash, cd, clang, make, and python; tests/smoke.sh
(make smoke), run in CI against each architecture's build before pushing
to Docker Hub; and installs bsdextrautils, colorized-logs, file, expect,
and fzf, dropping the Python help50 package.

Squashed from 99 commits, including #244, #245, #246, #247, and #248.

Co-authored-by: Rongxin Liu <10591665+rongxin-liu@users.noreply.github.com>
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