From b3bb54379833d3d7708bdd07d2c923ff4f52a7c0 Mon Sep 17 00:00:00 2001 From: Eitan <29402096+EitanWong@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:05:35 +0800 Subject: [PATCH] fix: human-readable novice error paths, covered by automated smoke Simulated a non-technical user hitting every common failure and found two defects plus a test-script bug: - rdev-host serve without --join-code printed a developer leftover ('rdev-host foreground placeholder') instead of actionable guidance - host join errors ignored the gateway's user_summary field and showed terse protocol text; now prefer the human-readable summary - unreachable-gateway error reworded from 'no healthy gateway routes: probes did not find a reachable candidate' to plain guidance ux-smoke.sh now exercises five novice error paths (unknown command, missing --join-code, bad join code, unreachable gateway, port in use) and asserts each message is human-readable and actionable. --- internal/hostcmd/exitcode.go | 8 +++++++- internal/hostcmd/hostcmd.go | 3 +-- internal/hostcmd/route_pool.go | 2 +- scripts/ux-smoke.sh | 36 +++++++++++++++++++++++++++++++++- 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/internal/hostcmd/exitcode.go b/internal/hostcmd/exitcode.go index f85cfda6..e29e79ba 100644 --- a/internal/hostcmd/exitcode.go +++ b/internal/hostcmd/exitcode.go @@ -46,7 +46,13 @@ func NewJoinSessionResponseError(statusCode int, status string, body []byte, cau message := gatewayErrorMessage(status, body, cause) protocolErr, complete := completeProtocolErrorEnvelope(body) if complete { - message = protocolErr.Message + // user_summary is the human-readable copy written for operators; + // prefer it over the terse protocol message. + if strings.TrimSpace(protocolErr.UserSummary) != "" { + message = protocolErr.UserSummary + } else { + message = protocolErr.Message + } } joinErr := fmt.Errorf("join session failed: %s", message) if statusCode >= 400 && statusCode <= 499 && complete && !protocolErr.Recoverable { diff --git a/internal/hostcmd/hostcmd.go b/internal/hostcmd/hostcmd.go index 1d5ec599..26a448d1 100644 --- a/internal/hostcmd/hostcmd.go +++ b/internal/hostcmd/hostcmd.go @@ -171,8 +171,7 @@ func (a App) runServe(ctx context.Context, opts serveOptions) error { return err } if opts.JoinCode == "" { - _, err := fmt.Fprintf(a.Stdout, "rdev-host foreground placeholder\nmode=%s\ngateway=%s\nstatus=not-connected\nnote=provide --gateway and --join-code for a Control Plane session\n", opts.Mode, opts.GatewayURL) - return err + return fmt.Errorf("--join-code is required; ask the operator (or the Agent) for a session join code, then run: rdev host serve --join-code CODE --gateway URL") } if strings.TrimSpace(opts.GatewayURL) == "" { return fmt.Errorf("gateway is required when --join-code is provided") diff --git a/internal/hostcmd/route_pool.go b/internal/hostcmd/route_pool.go index 5196a56a..50b18b61 100644 --- a/internal/hostcmd/route_pool.go +++ b/internal/hostcmd/route_pool.go @@ -12,7 +12,7 @@ import ( "github.com/EitanWong/remote-dev-skillkit/internal/controlplane" ) -var errNoHealthyRoutes = errors.New("no healthy gateway routes") +var errNoHealthyRoutes = errors.New("cannot reach the gateway; check the gateway URL and your network connection") const ( maxGatewayRoutes = 16 diff --git a/scripts/ux-smoke.sh b/scripts/ux-smoke.sh index df9264ce..438e6c47 100755 --- a/scripts/ux-smoke.sh +++ b/scripts/ux-smoke.sh @@ -144,7 +144,41 @@ else bad "task round trip failed: $(printf '%s' "${RESULT:-}" | head -c 300)" fi -# 6. close +# 7. error paths a novice will hit -- assert human-readable guidance +step "exercising novice error paths" +OUT="" +OUT="$("$WORK/rdev" bogus 2>&1 || true)" +if [[ "$OUT" == *"available commands"* ]]; then + ok "unknown command names available commands" +else + bad "unknown command error lacks guidance: $(printf '%s' "$OUT" | head -c 120)" +fi +OUT="$("$WORK/rdev-host" serve 2>&1 || true)" +if [[ "$OUT" == *"--join-code is required"* ]]; then + ok "missing --join-code explains what to do" +else + bad "missing --join-code error not actionable: $(printf '%s' "$OUT" | head -c 120)" +fi +OUT="$(timeout 8 "$WORK/rdev-host" serve --join-code ABCD-1234 --gateway "$GW" 2>&1 || true)" +if [[ "$OUT" =~ invalid|no\ longer\ active ]]; then + ok "bad join code error is human-readable" +else + bad "bad join code error unclear: $(printf '%s' "$OUT" | head -c 120)" +fi +OUT="$(timeout 8 "$WORK/rdev-host" serve --join-code ABCD-1234 --gateway 'http://127.0.0.1:9' 2>&1 || true)" +if [[ "$OUT" == *"cannot reach the gateway"* ]]; then + ok "unreachable gateway error is human-readable" +else + bad "unreachable gateway error unclear: $(printf '%s' "$OUT" | head -c 120)" +fi +OUT="$("$WORK/rdev" gateway serve --dev --addr "127.0.0.1:${PORT}" 2>&1 || true)" +if [[ "$OUT" == *"already in use"* || "$OUT" == *"try a different port"* ]]; then + ok "port-in-use error suggests an alternative" +else + bad "port-in-use error lacks guidance: $(printf '%s' "$OUT" | head -c 120)" +fi + +# 8. close step "closing session" if curl -sf -X POST "${GW}/v1/sessions/${SID}/close" -H 'Content-Type: application/json' -d '{"reason":"ux smoke complete"}' >/dev/null 2>&1; then ok "session closed"