Skip to content

-o tsv emits CRLF on Windows, breaking for x in $(az ... -o tsv) shell loops with misleading 400 errors #33385

Description

Describe the bug

On Windows, az ... --output tsv writes each row terminated by CRLF (\r\n) instead of LF (\n). When that output is consumed by a POSIX shell (Git Bash / MSYS2 / WSL-on-Windows-files / bash-from-PowerShell) via for x in $(...), every token except the last carries a stray \r. That \r gets interpolated into the URL path of the next az call, and ARM responds with HTTP 400 Bad Request. The CLI's error message — Operation returned an invalid status 'Bad Request' — does not reveal the underlying cause.

Net effect: a common shell pattern shown in many Microsoft Learn tutorials silently fails on Windows for every iteration except the last one in the loop.

To Reproduce

Any namespace/topic combination works. Example using Service Bus:

RG=<your-rg>
NS=<your-namespace>

for topic in $(az servicebus topic list --resource-group "$RG" --namespace-name "$NS" --query "[].name" -o tsv); do
  count=$(az servicebus topic subscription list --resource-group "$RG" --namespace-name "$NS" --topic-name "$topic" --query "length(@)" -o tsv 2>&1)
  printf "%-40s %s\n" "$topic" "$count"
done

Actual output (14 topics, only the alphabetically last works):

topic1                        ERROR: Operation returned an invalid status 'Bad Request'
topic2                         ERROR: Operation returned an invalid status 'Bad Request'

Environment summary

azure-cli                         2.80.0

core                              2.80.0
telemetry                          1.1.0

Extensions:
account                            0.2.5
authV2                             1.0.1
azure-devops                       1.0.2
redisenterprise                    1.4.0
subscription                       0.1.3

Dependencies:
msal                            1.34.0b1
azure-mgmt-resource               23.3.0

Python location 'C:\Program Files\Microsoft SDKs\Azure\CLI2\python.exe'

OS:     Windows 11 Pro 10.0.26200
Shell:  Git Bash (MSYS2 / mintty); also reproducible from PowerShell-invoked bash subshells

Proof of root cause

az ... -o tsv writes CRLF row terminators on Windows:

The bash `$(...)` substitution then carries `\r` on every token except the last (bash strips a trailing `\n` and the `\r` just before it in that single edge case — the other tokens keep their `\r` because the `\r` precedes a non-terminal `\n`).

When the next `az` call receives `--topic-name "$topic"`, the inner CLI inserts the `\r` into the ARM REST URL path. ARM responds 400. The CLI surfaces only `Operation returned an invalid status 'Bad Request'` and does not echo the ARM error body, so the cause is invisible to the user.

### Workaround

Strip `\r` from the captured output:

```bash
for topic in $(az servicebus topic list --resource-group "$RG" --namespace-name "$NS" --query "[].name" -o tsv | tr -d '\r'); do
  count=$(az servicebus topic subscription list --resource-group "$RG" --namespace-name "$NS" --topic-name "$topic" --query "length(@)" -o tsv)
  printf "%-40s %s\n" "$topic" "$count"
done

Proof of root cause

az ... -o tsv writes CRLF row terminators on Windows:

$ az servicebus topic list --resource-group "$RG" --namespace-name "$NS" --query "[].name" -o tsv | head -3 | od -c
0000000   t   o   p   i   c   1  \r  \n   t   o   p   i   c   2  \r  \n
0000020   t   o   p   i   c   3  \r  \n
0000030

After applying | tr -d '\r' the loop returns the expected counts for all 14 topics.

Comparison of output formats on Windows

Output mode Line ending Safe in shell for loops?
-o tsv CRLF No — this bug
-o json LF n/a (not row-delimited)
-o jsonc LF n/a
-o yaml LF n/a
-o table CRLF (decorative) n/a

Only the machine-parseable, row-delimited format tsv is affected. csv likely has the same issue but I have not retested it for this report.

Suggested fix

The CLI's tsv formatter inherits Python's default text-mode line translation on Windows. Two options:

  1. Reconfigure stdout for tsv/csv output — at format-dispatch time, call sys.stdout.reconfigure(newline='\n') when the user selected tsv or csv. Smallest change, easiest to review.
  2. Write tsv as bytes — sys.stdout.buffer.write(line.encode() + b'\n'). More invasive but eliminates any platform-specific newline handling.

Option 1 should be a one-line change in the format dispatch (azure-cli-core/azure/cli/core/commands/__init__.py or wherever the formatter lookup happens).

Bonus suggestion (separate issue territory)

When the underlying ARM call returns 4xx, the CLI should print the ARM error body, not just Operation returned an invalid status 'Bad Request'. ARM almost always includes a code and message field that would have pointed at the bad URL character immediately. The current behavior of swallowing those fields turns a 30-second diagnosis into a 30-minute one.

Why this matters

The pattern for x in $(az ... -o tsv); do ...; done appears verbatim in Microsoft Learn tutorials, Azure samples, and many blog posts. Every Windows user copying those snippets hits this bug and assumes their script, their credentials, their permissions, or their resource state is wrong. Few think "the CLI's tsv output has the wrong line endings." A one-line formatter fix removes a recurring scripting trap.

Anything else

Confirmed not present in PowerShell — PowerShell splits tsv output on [\r\n]+, masking the bug. The issue is bash-on-Windows-specific in observed effect, but the root cause (CRLF emission by the CLI on Windows) is the CLI's responsibility, not bash's.



### Related command

```bash
RG=<your-rg>
NS=<your-namespace>

for topic in $(az servicebus topic list --resource-group "$RG" --namespace-name "$NS" --query "[].name" -o tsv); do
  count=$(az servicebus topic subscription list --resource-group "$RG" --namespace-name "$NS" --topic-name "$topic" --query "length(@)" -o tsv 2>&1)
  printf "%-40s %s\n" "$topic" "$count"
done

### Errors

Actual output (14 topics, only the alphabetically last works):

topic1 ERROR: Operation returned an invalid status 'Bad Request'
topic2 ERROR: Operation returned an invalid status 'Bad Request'

Issue script & Debug output

Actual output (14 topics, only the alphabetically last works):

topic1                        ERROR: Operation returned an invalid status 'Bad Request'
topic2                         ERROR: Operation returned an invalid status 'Bad Request'

### Expected behavior

### Comparison of output formats on Windows

| Output mode | Line ending | Safe in shell `for` loops? |
|---|---|---|
| `-o tsv` | **CRLF** | **No — this bug** |
| `-o json` | LF | n/a (not row-delimited) |
| `-o jsonc` | LF | n/a |
| `-o yaml` | LF | n/a |
| `-o table` | CRLF (decorative) | n/a |

Only the **machine-parseable, row-delimited** format `tsv` is affected. `csv` likely has the same issue but I have not retested it for this report.

### Environment Summary


azure-cli 2.80.0

core 2.80.0
telemetry 1.1.0

Extensions:
account 0.2.5
authV2 1.0.1
azure-devops 1.0.2
redisenterprise 1.4.0
subscription 0.1.3

Dependencies:
msal 1.34.0b1
azure-mgmt-resource 23.3.0

Python location 'C:\Program Files\Microsoft SDKs\Azure\CLI2\python.exe'

OS: Windows 11 Pro 10.0.26200
Shell: Git Bash (MSYS2 / mintty); also reproducible from PowerShell-invoked bash subshells


### Additional context

_No response_

Activity

  1. added
    bugThis issue requires a change to an existing behavior in the product in order to be resolved.
    on May 15, 2026
  2. azure-client-tools-bot-prd commented on May 15, 2026

    @azure-client-tools-bot-prd

    Hi Dmytro Slotvinskyi (@dmytroslotvinskyy),

    2.80.0 is not the latest Azure CLI(2.86.0).

    If you haven't already attempted to do so, please upgrade to the latest Azure CLI version by following https://learn.microsoft.com/en-us/cli/azure/update-azure-cli.

  3. yonzhan commented on May 15, 2026

    @yonzhan
    Collaborator

    Thank you for opening this issue, we will look into it.

  4. github-actions commented on May 15, 2026

    @github-actions

    Here are some similar issues that might help you. Please check if they can solve your problem.


    Possible solution (Extracted from existing issue, might be incorrect; please verify carefully)

    Solution 1:

    This is an issue of WSL, not Azure CLI. You may provide your feedback in microsoft/WSL#7248.

    Reference:

    Solution 2:

    Please see the explanation at #13573 (comment).

    Reference:

    Powered by issue-sentinel

  5. dmytroslotvinskyy commented on May 15, 2026

    @dmytroslotvinskyy
    Author

    Yong Zhang (@yonzhan) I`ve provided my findings, because it was annoying to get 400 from cli, and got workaround. Thanks

  6. azclibot commented on Jun 22, 2026

    @azclibot
    Collaborator

    Hi Dmytro Slotvinskyi (@dmytroslotvinskyy), thanks for the detailed report!

    Root cause: On Windows, Python's text-mode file I/O writes CRLF line endings when writing to stdout. The -o tsv formatter writes rows this way, so each row ends with . When a POSIX shell (Git Bash/MSYS2) captures this output via $(...), the is embedded in most tokens and gets silently interpolated into subsequent CLI calls, causing ARM to return HTTP 400.

    Fix needed: In the TSV output formatter (azure/cli/core/azclierror.py or the relevant formatter), ensure line endings are always LF ( ), not CRLF, regardless of platform. This could be done by opening stdout in binary mode for TSV output, or by explicitly writing instead of relying on print() which uses the platform default.

    Copilot has been assigned to investigate and implement the fix in the output module of azure-cli-core.


    Posted by agent-assist (autonomous bug-fix pipeline).

  7. philsphicas commented on Oct 16, 2026

    @philsphicas
    Contributor

    PR #34236 fixes this. On Windows, Python's stdout converts \n to \r\n, so this affects every output format (not just tsv), and CRLF already in the data comes out as \r\r\n. The PR adds core.output_newline (native | lf) and makes lf the default when az is run from WSL or Git Bash; cmd/PowerShell output is unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Auto-AssignAuto assign by botAuto-ResolveAuto resolve by botOutputPossible-SolutionService AttentionThis issue is responsible by Azure service team.Service Busaz servicebusSimilar-Issueact-observability-squadbugThis issue requires a change to an existing behavior in the product in order to be resolved.customer-reportedIssues that are reported by GitHub users external to the Azure organization.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions