Skip to content

[serial-console] Fix garbled console output by decoding binary websocket frames#9797

Open
muram wants to merge 5 commits intoAzure:mainfrom
muram:fix/serial-console-bytes-decode
Open

[serial-console] Fix garbled console output by decoding binary websocket frames#9797
muram wants to merge 5 commits intoAzure:mainfrom
muram:fix/serial-console-bytes-decode

Conversation

@muram
Copy link
Copy Markdown

@muram muram commented Apr 19, 2026

Related command

az serial-console connect

Description

Fixes #9796.

The serial-console extension's on_message callback in SerialConsole.connect() assumed websocket frames were always str. Newer versions of websocket-client (>= 1.0) return bytes for binary frames, which the extension passed straight into Python's built-in print() — producing a bytes repr (b'...\r\n\x1b[0;32m...') in the user's terminal instead of decoded text. This makes the serial console unusable on recent Python / websocket-client combinations (reliably reproduced on Python 3.13).

Sample of the broken output before this fix:

b'\r\n[ 2135.909432] cloud-init[3293]: The system is finally up...\r\n\x1b[0;32m  OK  \x1b[0m] Finished cloud-init...'

Runtime fix

In src/serial-console/azext_serialconsole/custom.py, decode bytes/bytearray to str (UTF-8 with errors="replace" for safety) before forwarding the message to PC.print. After this change, ANSI escapes and CR/LF are interpreted by the user's terminal as intended and the serial console renders normally.

def on_message(_, message):
    if isinstance(message, (bytes, bytearray)):
        message = bytes(message).decode("utf-8", errors="replace")
    ...

Dependency widening (required for clean install)

The websocket-client requirement is widened from ==1.3.1 to ~=1.8.0. Current azure-cli requires websocket-client~=1.8.0 (src/azure-cli/setup.py, pinned to 1.8.0 in requirements.py3.Linux.txt), so the existing ==1.3.1 pin produces a pkg_resources.ContextualVersionConflict when the extension is added against current azure-cli:

pkg_resources.ContextualVersionConflict: (websocket-client 1.3.1 ...,
    Requirement.parse('websocket-client~=1.8.0'), {'azure-cli'})
Exception: Error when adding serial-console from source
    https://azcliprod.blob.core.windows.net/cli-extensions/serial_console-1.0.0b3-py3-none-any.whl

This blocks version-cal in CI and means published 1.0.0b3 is currently uninstallable on a fresh azure-cli. The runtime API used by this extension (WebSocketApp.on_message) is stable across the 1.x line, so widening to ~=1.8.0 is safe.

Test cassette refresh

Both VCR cassettes (test_check_resource_VM.yaml, test_check_resource_VMSS.yaml) had stale aliases.json request URIs (raw.githubusercontent.com/.../aliases.json). azure-cli now fetches that file from azcliprod.blob.core.windows.net/cli/vm/aliases.json, so the cassette URIs were swapped to match. Response bodies and headers are unchanged. Without this, Build Tests Python3.10–3.13 fail with VCR Can't overwrite existing cassette. Same change pattern already landed in src/scheduled-query/ cassettes.

Versioning

  • Bumps serial-console from 1.0.0b3 to 1.0.0b4.
  • Adds a HISTORY.rst entry covering the runtime fix and the dependency change.

Testing

  • Repro before fix: az serial-console connect --resource-group <rg> --name <vmss> --instance-id <id> shows literal b'...' blob instead of console output.
  • After applying this patch (and clearing __pycache__), the same command renders the cloud-init / login boot log normally with proper newlines and colors.
  • Verified on:
    • macOS 15 (Apple Silicon), Homebrew azure-cli 2.85.0, Python 3.13.12
    • Connecting to a Linux VMSS instance with serial console enabled

Notes

  • The runtime fix itself is minimal and behavior-preserving (no API changes).
  • Pinning websocket-client < 1.0 would also "fix" the bytes-decode symptom but is not future-proof and conflicts with azure-cli's own pinning.

@azure-client-tools-bot-prd
Copy link
Copy Markdown

azure-client-tools-bot-prd Bot commented Apr 19, 2026

️✔️Azure CLI Extensions Breaking Change Test
️✔️Non Breaking Changes

@yonzhan
Copy link
Copy Markdown
Collaborator

yonzhan commented Apr 19, 2026

Thank you for your contribution! We will review the pull request and get back to you soon.

@github-actions
Copy link
Copy Markdown
Contributor

The git hooks are available for azure-cli and azure-cli-extensions repos. They could help you run required checks before creating the PR.

Please sync the latest code with latest dev branch (for azure-cli) or main branch (for azure-cli-extensions).
After that please run the following commands to enable git hooks:

pip install azdev --upgrade
azdev setup -c <your azure-cli repo path> -r <your azure-cli-extensions repo path>

@microsoft-github-policy-service microsoft-github-policy-service Bot added customer-reported Issues that are reported by GitHub users external to the Azure organization. Auto-Assign Auto assign by bot labels Apr 19, 2026
@muram
Copy link
Copy Markdown
Author

muram commented Apr 19, 2026

@microsoft-github-policy-service agree

@yonzhan
Copy link
Copy Markdown
Collaborator

yonzhan commented Apr 19, 2026

Please fix CI issues

muram added a commit to muram/azure-cli-extensions that referenced this pull request Apr 20, 2026
…conflicts

Fix CI failure on PR Azure#9797:

1. The serial-console extension pinned `websocket-client==1.3.1`, but
   recent azure-cli requires `websocket-client~=1.8.0`. Installing the
   extension into an env that already has azure-cli triggers
   `pkg_resources.ContextualVersionConflict` and `azdev extension add`
   fails. Widen the pin to `~=1.8.0` to match azure-cli and stay within
   the same major series.

2. In `.github/workflows/VersionCalPRComment.yml`, `azdev setup` runs
   after `pip install azdev` and installs azure-cli's pinned
   requirements, which downgrade transitive deps that azdev itself
   relies on:
       - azure-cli-diff-tool 0.1.1 needs requests~=2.32.3
         but azure-cli pins requests==2.33.0
       - tox 4.53.0 needs packaging>=26
         but azure-cli pins packaging==25.0
   Re-pin those two packages after `azdev setup` so the environment is
   internally consistent before the metadata generation step runs. Also
   add a non-fatal `pip check` for visibility.

Made-with: Cursor
@github-actions
Copy link
Copy Markdown
Contributor

@muram
Copy link
Copy Markdown
Author

muram commented Apr 20, 2026

Pushed a follow-up commit to fix the failing version-cal job:

  1. Real blockerpkg_resources.ContextualVersionConflict: (websocket-client 1.3.1 ..., Requirement.parse('websocket-client~=1.8.0'), {'azure-cli'}). The extension pinned websocket-client==1.3.1 while current azure-cli requires ~=1.8.0. Widened the pin in src/serial-console/setup.py to websocket-client~=1.8.0 and noted it in HISTORY.rst.

  2. Workflow tidy-up.github/workflows/VersionCalPRComment.yml "Install azdev" step ends up with two mismatched transitive deps after azdev setup -c azure-cli runs (azure-cli's requirements.py3.Linux.txt downgrades them):

    • azure-cli-diff-tool 0.1.1 requires requests~=2.32.3, but you have requests 2.33.0
    • tox 4.53.0 requires packaging>=26, but you have packaging 25.0

    These weren't the actual blocker, but they leave the env inconsistent and make the failure log noisier than it needs to be. Added a pip install --upgrade "requests~=2.32.3" "packaging>=26" after azdev setup, plus a non-fatal pip check for visibility. Happy to split the workflow change into a separate PR if maintainers prefer.

Re-running CI now.

@necusjz
Copy link
Copy Markdown
Member

necusjz commented Apr 20, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

@jimdigriz
Copy link
Copy Markdown

@muram thanks for this fix, life much improved for me!

@yonzhan
Copy link
Copy Markdown
Collaborator

yonzhan commented May 6, 2026

Please fix CI issues

muram added 4 commits May 8, 2026 19:44
…ket frames

The serial-console extension's on_message callback assumed websocket
frames were always str. Newer versions of websocket-client (>= 1.0)
return bytes for binary frames, which the extension passed straight
into print() and got rendered as a Python bytes repr (b'...\r\n
\x1b[0;32m...'). This makes the serial console unusable on recent
Python/websocket-client combinations (reproduced on Python 3.13 with
the vendored websocket-client 1.3.1).

Decode bytes to str inside on_message so that ANSI escapes and CR/LF
are interpreted by the user's terminal as intended.

Bumps version to 1.0.0b4 and adds a HISTORY.rst entry.

Fixes Azure#9796

Made-with: Cursor
…conflicts

Fix CI failure on PR Azure#9797:

1. The serial-console extension pinned `websocket-client==1.3.1`, but
   recent azure-cli requires `websocket-client~=1.8.0`. Installing the
   extension into an env that already has azure-cli triggers
   `pkg_resources.ContextualVersionConflict` and `azdev extension add`
   fails. Widen the pin to `~=1.8.0` to match azure-cli and stay within
   the same major series.

2. In `.github/workflows/VersionCalPRComment.yml`, `azdev setup` runs
   after `pip install azdev` and installs azure-cli's pinned
   requirements, which downgrade transitive deps that azdev itself
   relies on:
       - azure-cli-diff-tool 0.1.1 needs requests~=2.32.3
         but azure-cli pins requests==2.33.0
       - tox 4.53.0 needs packaging>=26
         but azure-cli pins packaging==25.0
   Re-pin those two packages after `azdev setup` so the environment is
   internally consistent before the metadata generation step runs. Also
   add a non-fatal `pip check` for visibility.

Made-with: Cursor
This change was bundled into 22c4dfc to silence transitive `requests`
and `packaging` warnings emitted after `azdev setup` reinstalls
azure-cli's pinned requirements. The actual `version-cal`
ContextualVersionConflict was already resolved by the
`websocket-client~=1.8.0` bump in src/serial-console/setup.py;
the workflow patch only quieted log noise.

A repo-wide CI workflow tweak doesn't belong in a serial-console
extension PR. Reverting so this PR is scoped to the bytes-decode fix
and its supporting metadata changes only.
CI Build Tests for Python 3.10–3.13 fail with VCR `Can't overwrite
existing cassette` because `az vm create` / `az vmss create` now fetch
the image-aliases JSON from
`https://azcliprod.blob.core.windows.net/cli/vm/aliases.json` while the
recorded cassettes still contain the old
`https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/arm-compute/quickstart-templates/aliases.json`
URL. The response body is unchanged, so swap the request URI to match
the live endpoint (matches the same change already landed in the
scheduled-query cassettes).

Affects:
- src/serial-console/azext_serialconsole/tests/latest/recordings/test_check_resource_VM.yaml
- src/serial-console/azext_serialconsole/tests/latest/recordings/test_check_resource_VMSS.yaml
@muram muram force-pushed the fix/serial-console-bytes-decode branch from 3116ea1 to f7767fb Compare May 9, 2026 00:48
Copilot AI review requested due to automatic review settings May 9, 2026 00:48
@muram
Copy link
Copy Markdown
Author

muram commented May 9, 2026

Pushed two follow-up commits to address the failing Azure Pipelines Build Tests (Python 3.10–3.13) and to tighten the PR scope. Branch was also rebased onto current main.

1. Refresh stale VCR cassettes (real fix for Build Tests failures)

Both test_check_resource_VM and test_check_resource_VMSS were failing with:

Can't overwrite existing cassette ('test_check_resource_VMSS.yaml') in your current record mode ('once').
No match for the request (<Request (GET) https://azcliprod.blob.core.windows.net/cli/vm/aliases.json>) was found.
Found 2 similar requests with 2 different matcher(s) :
1 - (<Request (GET) https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/arm-compute/quickstart-templates/aliases.json>).

az vm create / az vmss create now fetch the image-aliases JSON from https://azcliprod.blob.core.windows.net/cli/vm/aliases.json (an azure-cli change), but the recorded cassettes still pointed at the old raw.githubusercontent.com URL. The response body is identical, so the fix is a one-line URI swap in each of the two cassette files — the same pattern already landed in src/scheduled-query/.../test_scheduled_query.yaml. No response data was modified.

2. Revert out-of-scope workflow change

The previous push touched .github/workflows/VersionCalPRComment.yml to silence transitive requests / packaging warnings after azdev setup. The actual version-cal ContextualVersionConflict was already resolved by the websocket-client~=1.8.0 bump in setup.py; the workflow patch only quieted log noise. A repo-wide CI workflow tweak doesn't belong in a serial-console extension PR, so I've reverted it. If the warning cleanup is genuinely wanted, happy to send it as a separate scoped PR.

Final PR scope (vs current main)

src/serial-console/HISTORY.rst                                                          | +5
src/serial-console/azext_serialconsole/custom.py                                        | +5
src/serial-console/azext_serialconsole/tests/latest/recordings/test_check_resource_VM.yaml   | +2 -2
src/serial-console/azext_serialconsole/tests/latest/recordings/test_check_resource_VMSS.yaml | +2 -2
src/serial-console/setup.py                                                             | +2 -2
5 files changed, 16 insertions(+), 6 deletions(-)

GitHub Actions checks (azdev-linter, azdev-style, version-cal, pr-code-review) are running. Azure Pipelines needs a /azp run to re-trigger. cc @yonzhan @necusjz

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 az serial-console connect output corruption on newer websocket-client versions by decoding binary websocket frames (bytes/bytearray) into UTF-8 text before printing, restoring correct rendering of newlines and ANSI escape sequences.

Changes:

  • Decode binary websocket frames to str in SerialConsole.connect()’s on_message callback before calling PC.print.
  • Bump the serial-console extension version to 1.0.0b4 and update HISTORY.rst.
  • Update recorded test traffic to use the current VM aliases endpoint.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/serial-console/azext_serialconsole/custom.py Decode bytes/bytearray websocket messages to UTF-8 text before printing to the terminal.
src/serial-console/setup.py Bump extension version; update websocket-client dependency constraint.
src/serial-console/HISTORY.rst Add 1.0.0b4 release notes describing the console output fix and dependency bump.
src/serial-console/azext_serialconsole/tests/latest/recordings/test_check_resource_VM.yaml Refresh recording URIs for VM aliases lookup.
src/serial-console/azext_serialconsole/tests/latest/recordings/test_check_resource_VMSS.yaml Refresh recording URIs for VMSS aliases lookup.

Comment thread src/serial-console/setup.py
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread src/serial-console/HISTORY.rst Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Auto-Assign Auto assign by bot customer-reported Issues that are reported by GitHub users external to the Azure organization. Serial Console

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[serial-console] Garbled output: bytes printed as repr() instead of decoded text

5 participants