Skip to content

ESD-1666: Route Cobra command errors to result.error in the WASM CLI#542

Open
Phil-Browne wants to merge 3 commits into
mainfrom
worktree-esd-1666-route-cobra-errors-to-result-error
Open

ESD-1666: Route Cobra command errors to result.error in the WASM CLI#542
Phil-Browne wants to merge 3 commits into
mainfrom
worktree-esd-1666-route-cobra-errors-to-result-error

Conversation

@Phil-Browne

@Phil-Browne Phil-Browne commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Parser-layer failures in the WASM CLI (unknown command, invalid flags, missing required args) wrote the error into result.output and left result.error empty. The Portal keys success/failure off result.error, so those failures rendered uncolored and never fired the cli_terminal_command_failed telemetry event (ENG-33590). This routes the failure to result.error instead.

  • ExecuteWithArgs now returns the failure error; the async bridge sets result.error from it and keeps only the usage hint in result.output.
  • The root command's unknown-command handling is now RunE returning a real error, instead of printing Error: ... to the output buffer (that was why location list never reached result.error).
  • SilenceErrors is set so cobra doesn't stream its own Error: line before we can route it (which would double-render under a live-output handler).
  • The JSON-envelope and streamed-error paths still return nil, so an error already shown to the terminal isn't rendered twice.
  • result.error (and the panic-recovery error) is sanitized with SanitizeTerminalText, which strips every control byte: a parser error can echo a user-typed flag name verbatim and the host writes result.error straight to xterm, and the host styles the error line itself so no escape sequence needs to survive.
  • Made the WASM root-package tests run under bare node: added an in-memory localStorage shim in TestMain (the config-file and localStorage tests previously panicked, which masked later failures) and repointed two tests off the now-unavailable version command.

No Portal-side changes required.

Verified against Portal source (web-monorepo)

  • The Portal writes result.error straight to xterm via terminal.write(\\x1b[31mError: ${result.error}\x1b[0m\r\n`) (useXtermShell.ts), no HTML escaping, wrapped in its own red SGR. So the sanitization is genuinely required and SanitizeTerminalText` (strip all control bytes) is the right choice: the host supplies the color, nothing in the message should survive.
  • Telemetry (onError) fires only when result.error is non-empty (useXtermShell.ts -> useCliTelemetry.ts, event cli_terminal_command_result_error), confirming the fix is what makes failure telemetry fire.
  • The Portal never registers an output handler (only a prompt handler), so in production HasOutputHandler() is false and every failure takes the capture path. That means command-execution errors (an action calling output.PrintError) also route to result.error there, so telemetry fires for those too. The streamed-error "no telemetry" case only applies to a hypothetical consumer that registers a live-output handler, which the Portal does not.

Parser-layer failures (unknown command, invalid flags, missing required
args) wrote the error text into result.output and left result.error empty,
so the Portal rendered failures uncolored and never fired the
cli_terminal_command_failed telemetry event.

ExecuteWithArgs now returns the failure error, and the async bridge routes
it to result.error while keeping only the usage hint in output. The root
command's unknown-command handling becomes RunE so it surfaces a real
error instead of printing to the buffer, and SilenceErrors is set so cobra
does not stream the error before it can be routed. The JSON-envelope and
streamed-error paths still return nil to avoid double-rendering.

The error string is sanitized before it reaches result.error, since a
parser error can echo a user-typed flag name verbatim and the host writes
result.error straight to xterm.
Copilot AI review requested due to automatic review settings July 17, 2026 13:50
@Phil-Browne
Phil-Browne requested review from a team and penzeliz-megaport as code owners July 17, 2026 13:50
@Phil-Browne
Phil-Browne requested a review from ianb-mp July 17, 2026 13:50
@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.50%. Comparing base (20955b2) to head (7299342).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #542   +/-   ##
=======================================
  Coverage   79.50%   79.50%           
=======================================
  Files         193      193           
  Lines       18859    18859           
=======================================
  Hits        14993    14993           
  Misses       2818     2818           
  Partials     1048     1048           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes WASM CLI “parser-layer” failures (unknown command, invalid flags, missing required args) so they populate result.error (instead of being buried in result.output), enabling the Portal to correctly render failures as errors and emit failure telemetry. It also ensures error strings routed to result.error are sanitized to prevent terminal control-sequence injection.

Changes:

  • Update the WASM async bridge to set result.error from ExecuteWithArgs’ returned error (sanitized), while keeping only the usage hint in result.output.
  • Change WASM ExecuteWithArgs to return an error when it should be surfaced by the host, and set rootCmd.SilenceErrors = true to prevent Cobra from pre-printing Error: lines.
  • Add/adjust WASM tests to enforce the new error-routing contract and sanitization behavior.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
File Description
main_wasm.go Routes returned command errors into result.error (sanitized) in the async JS bridge.
main_wasm_test.go Adds bridge-level regression tests for unknown-command routing and sanitization.
cmd/megaport/megaport_wasm.go Changes ExecuteWithArgs to return an error for host routing; silences Cobra error printing; preserves JSON/streamed no-double-render behavior.
cmd/megaport/common_wasm.go Switches unknown-command handling to RunE returning real errors (instead of writing Error: into the output buffer).
cmd/megaport/error_routing_wasm_test.go Adds contract tests covering parser-layer routing, handler behavior, and streamed-error no-double-render.
cmd/megaport/unknown_flags_wasm_test.go Updates unknown-flag test to assert error is returned (not left in captured output).
cmd/megaport/session_expired_wasm_test.go Adjusts call sites for new ExecuteWithArgs return signature.
cmd/megaport/nat_gateway_wasm_test.go Adjusts call sites for new ExecuteWithArgs return signature.
cmd/megaport/modules_wasm_test.go Adjusts call sites for new ExecuteWithArgs return signature.
cmd/megaport/ix_wasm_test.go Adjusts call sites for new ExecuteWithArgs return signature.
cmd/megaport/colors_wasm_test.go Adjusts call sites for new ExecuteWithArgs return signature.

Review-pass follow-ups on the Cobra-error routing change:
- Sanitize result.error with SanitizeTerminalText (strip all control bytes)
  instead of the SGR-allowing SanitizeTerminalOutput; the host styles the
  error line itself, so no escape sequence should survive. Apply the same
  sanitization to the panic-recovery result.error.
- Cover the JSON-envelope branch (returns nil) and the browser-unavailable
  commands (config/completion/generate-docs/version) routing to result.error.
- Install an in-memory localStorage shim in TestMain so the config-file and
  localStorage tests run under bare node instead of panicking, and stop
  TestOutputBufferReset/TestConcurrentCommands from asserting the now-error
  'version' command succeeds.
The root package is now node-runnable via the localStorage shim, so add it
to the wasm.yml test list; the executeMegaportCommandAsync bridge tests that
most directly exercise the result.error contract now gate in CI. Also trim
three over-long comments to the short WHY form.
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.

2 participants