Skip to content

fix(cli): let generate markdown write to stdout - #766

Merged
jdx merged 1 commit into
jdx:mainfrom
JamBalaya56562:fix-markdown-stdout
Aug 2, 2026
Merged

fix(cli): let generate markdown write to stdout#766
jdx merged 1 commit into
jdx:mainfrom
JamBalaya56562:fix-markdown-stdout

Conversation

@JamBalaya56562

@JamBalaya56562 JamBalaya56562 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Problem

generate markdown is the only generator with no way to reach stdout — --out-file is required and nothing else emits the document:

Subcommand stdout?
markdown no--out-dir / --out-file only
manpage, fig yes, when the output flag is omitted
json, completion, completion-init stdout only

So the workaround is --out-file /dev/stdout. On unix that happens to work, because it is a real device file. On Windows it is just a relative-looking path, and xx::file::write creates the parent directory before writing — so it silently produces a real C:\dev\stdout file and prints nothing.

This repo's own test suite does it, at cli/tests/markdown.rs:202. On Windows that test leaves the stray file behind and fails; on unix it passes, but the snapshot it produced has writing to /dev/stdout baked in as its first line, because the progress message is printed to stdout and therefore lands inside the document.

Fix

  • generate markdown --out-file is now optional and defaults to stdout, matching manpage, fig, json and completion. This is the actual fix — it removes the reason to reach for /dev/stdout in the first place.
  • --out-file - means stdout on markdown, manpage and fig, mirroring the -f - convention already used for reading a spec from stdin. It matters for callers that build the path in a variable, and it gives Windows users a portable answer. A file literally named - is spelled ./-.
  • Both spellings go through one write_or_stdout helper placed directly below parse_file_or_stdin, so the input and output conventions read as each other's mirror.
  • The writing to … line moved from stdout to stderr on markdown, manpage, fig and sdk.
  • --out-dir now requires --multi. Without this, dropping the --out-file requirement would have made --out-dir on its own silently print to stdout and ignore the directory.

/dev/stdout is deliberately not special-cased on Windows. It is a unix fd path rather than a portable filename, and emulating it honestly drags in /dev/stderr, /dev/null, /dev/fd/N and append-vs-truncate semantics. - is the portable spelling and is now documented in the help text of all three flags.

Also left alone: generate fig wraps the spec in a prescript/postscript when writing to a file but prints the bare spec object to stdout. --out-file - follows the file path — it means "the bytes a file would have received" — and bare usage g fig is unchanged. Whether those two should agree is a pre-existing question, flagged in a comment rather than answered here.

Compatibility

No existing valid invocation changes meaning. The one behaviour change is that usage g md -f x.kdl with no output flag goes from a clap error to printing markdown, which is the point.

--out-file keeps effect=write on all three commands. The effect model is static per flag with no way to say "read when the value is -", and for a field that agents read to decide whether to ask permission, over-reporting is the safe direction.

Verified

Windows 11. In an empty directory:

$ usage g md -f basic.usage.kdl --out-file - | head -3
<!-- @generated by usage-cli from usage spec -->
# `basic.usage.kdl`

$ usage g md -f basic.usage.kdl | head -3        # identical
$ ls -A | wc -l
0

No C:\dev is created. cargo test -p usage-cli (markdown, manpage, clap_sort, lib) and cargo test -p usage-lib --all-features pass; cargo clippy --all --all-features -- -D warnings and cargo fmt --all -- --check are clean.

New tests, all platform-independent so they hold the line on the Linux CI:

  • test_markdown_out_file_dash_writes_no_file — runs with --out-file - in a fresh empty directory and asserts nothing was written. Before the fix this leaves a file named -, which is the same bug that produced C:\dev\stdout.
  • test_markdown_stdout_when_out_file_omitted — the two spellings of stdout produce identical bytes.
  • test_generate_markdown_basic now asserts the progress line is on stderr and not on stdout.
  • test_generate_manpage_out_file_dash_is_stdout-o - matches omitting -o.

The existing snapshot test switched from /dev/stdout to -; the only change to markdown__markdown_snapshot_with_examples.snap is the removal of the stray writing to /dev/stdout line.

Generated artifacts

Regenerated from the new help text: cli/usage.usage.kdl, docs/cli/reference/commands.json, docs/cli/reference/generate/{markdown,manpage,fig}.md, cli/assets/usage.1, cli/assets/fig.ts. The diff in each is the three help strings and nothing else. Completion scripts are untouched — they carry no flag help.

Making --out-file optional produces no artifact diff on its own: clap's required_unless_present never set ArgSettings::Required, and lib/src/spec/flag.rs:430 reads is_required_set(), so the spec already recorded the flag as not required.


This pull request was generated by Claude Code.

Summary by CodeRabbit

  • New Features

    • Generate Fig, manpage, and Markdown output directly to stdout with --out-file -.
    • Manpage and single-file Markdown generation now default to stdout when no output file is specified.
    • Output files can be written while progress messages remain separate on stderr.
  • Documentation

    • Clarified command help and reference documentation for stdout output, defaults, and file options.
  • Bug Fixes

    • Prevented stdout-generated Markdown from creating filesystem artifacts.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Fig, manpage, and Markdown generation now share stdout and file output handling. Markdown single-file output defaults to stdout. Progress messages use stderr, with tests and documentation updated.

Changes

Generator output handling

Layer / File(s) Summary
Shared output contract
cli/src/cli/generate/mod.rs, cli/src/cli/generate/fig.rs, cli/src/cli/generate/manpage.rs, cli/src/cli/generate/markdown.rs, cli/src/cli/generate/sdk.rs
write_or_stdout routes output to stdout or files. Generators use the shared writer, and file progress messages use stderr.
Output validation and coverage
cli/src/cli/generate/markdown.rs, cli/tests/manpage.rs, cli/tests/markdown.rs
Tests verify default and explicit stdout output, stderr progress messages, equivalent output, and the absence of filesystem artifacts.
CLI help and reference updates
cli/assets/*, cli/usage.usage.kdl, docs/cli/reference/*
Help text and reference documentation describe - as stdout and identify applicable defaults.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GeneratorCommand
  participant write_or_stdout
  participant stdout
  participant OutputFile
  GeneratorCommand->>write_or_stdout: pass generated contents and output path
  write_or_stdout->>stdout: write when path is missing or "-"
  write_or_stdout->>OutputFile: write when path is a file
Loading

Possibly related PRs

  • jdx/usage#751: Both PRs update generated CLI documentation for generate commands and output flags.

Poem

A rabbit checks the output stream,
Where files and stdout meet the dream.
Progress hops to stderr’s side,
Markdown flows with nothing to hide.
- opens the burrow wide.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 93.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: allowing generate markdown to write output to stdout.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@JamBalaya56562
JamBalaya56562 marked this pull request as ready for review August 2, 2026 00:59
@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds portable stdout output for Markdown, manpage, and Fig generation while routing progress messages to stderr.

  • Makes Markdown’s output file optional and defaults single-document output to stdout.
  • Treats --out-file - as stdout through a shared fallible writer.
  • Requires --multi when Markdown’s --out-dir is supplied.
  • Updates tests, generated CLI metadata, reference documentation, and bundled assets.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported stdout panic path now uses fallible writes, treats a closed downstream pipe as normal termination, and propagates other output errors.

Important Files Changed

Filename Overview
cli/src/cli/generate/mod.rs Adds the shared stdout/file writer; the follow-up fix suppresses only BrokenPipe and propagates all other write or flush errors without panicking.
cli/src/cli/generate/markdown.rs Makes single-file output default to stdout, preserves generated banners, routes file progress to stderr, and enforces the multi-output directory relationship.
cli/src/cli/generate/manpage.rs Routes optional file output and - through the shared fallible writer.
cli/src/cli/generate/fig.rs Adds --out-file - support while deliberately preserving the existing distinction between bare stdout output and file-form output.
cli/tests/markdown.rs Covers equivalent implicit and explicit stdout output, absence of filesystem artifacts, and stderr progress reporting.
cli/tests/manpage.rs Verifies that -o - matches the existing implicit stdout behavior.

Reviews (2): Last reviewed commit: "fix(cli): let generate markdown write to..." | Re-trigger Greptile

Comment thread cli/src/cli/generate/mod.rs Outdated
@JamBalaya56562
JamBalaya56562 marked this pull request as draft August 2, 2026 01:04
`generate markdown` was the only generator with no way to reach stdout:
`--out-file` was required and nothing else emitted the document. The
workaround people reach for is `--out-file /dev/stdout`, which is a device
file on unix but just a relative-looking path on Windows — `xx::file::write`
creates the parent directory first, so it leaves a real `C:\dev\stdout`
behind. This repo's own test suite did exactly that, and the resulting
snapshot had a stray `writing to /dev/stdout` line baked into its first line.

`--out-file` is now optional on markdown and defaults to stdout, matching
manpage, fig, json and completion. `-` also means stdout on markdown, manpage
and fig, mirroring the `-f -` convention already used for reading a spec from
stdin; a file literally named `-` is spelled `./-`. Both spellings go through
one `write_or_stdout` helper sitting next to `parse_file_or_stdin`.

That helper writes with `write_all` rather than `print!`, which panics when
the write fails. These documents outlast a pipe buffer — 100 KB for mise's
spec — so `usage g markdown -f mise.usage.kdl --out-file - | head -1` ended in
`failed printing to stdout … (os error 109)` and exit 101. A reader that
closed early is not reported: Rust ignores SIGPIPE, so what ends the process
silently in C arrives as an ordinary write error.

The `writing to ...` progress line moved from stdout to stderr on markdown,
manpage, fig and sdk. On stdout it lands inside the document as soon as the
document itself goes to stdout, which is what the snapshot was recording.

Dropping the `--out-file` requirement would have made `--out-dir` without
`--multi` silently print to stdout and ignore the directory, so `--out-dir`
now requires `--multi`.

`/dev/stdout` is deliberately not special-cased on Windows: it is a unix fd
path, and emulating it honestly pulls in `/dev/stderr`, `/dev/null` and
`/dev/fd/N` too. `-` is the portable spelling and is now in the help text.
@JamBalaya56562
JamBalaya56562 marked this pull request as ready for review August 2, 2026 01:23
@jdx
jdx merged commit 9de1c0c into jdx:main Aug 2, 2026
8 checks passed
@mise-en-dev mise-en-dev mentioned this pull request Aug 2, 2026
@JamBalaya56562
JamBalaya56562 deleted the fix-markdown-stdout branch August 2, 2026 02:02
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Aug 11, 2026
⚠️ **CAUTION: this is a major update, indicating a breaking change!** ⚠️

This MR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [usage](https://github.com/jdx/usage) | tools | major | `3.5.6` → `5.1.0` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>jdx/usage (usage)</summary>

### [`v5.1.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#510---2026-08-09)

[Compare Source](jdx/usage@v5.0.0...v5.1.0)

##### 🚀 Features

- **(spec)** parse usage comments from strings by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;782](jdx/usage#782)

##### 🐛 Bug Fixes

- **(spec)** avoid inferred metadata from included specs by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;786](jdx/usage#786)

##### 🧪 Testing

- **(windows)** make the suite runnable on Windows by [@&#8203;JamBalaya56562](https://github.com/JamBalaya56562) in [#&#8203;771](jdx/usage#771)

##### 📦️ Dependency Updates

- update rust crate rmcp to v3 by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;780](jdx/usage#780)

### [`v5.0.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#500---2026-08-02)

[Compare Source](jdx/usage@v4.1.0...v5.0.0)

##### 🚀 Features

- **(cli)** allow overriding the shell program with USAGE\_SHELL\_<SHELL> by [@&#8203;JamBalaya56562](https://github.com/JamBalaya56562) in [#&#8203;767](jdx/usage#767)

##### 🐛 Bug Fixes

- **(cli)** forward parsed args to WSL bash via WSLENV on windows by [@&#8203;JamBalaya56562](https://github.com/JamBalaya56562) in [#&#8203;764](jdx/usage#764)
- **(cli)** let generate markdown write to stdout by [@&#8203;JamBalaya56562](https://github.com/JamBalaya56562) in [#&#8203;766](jdx/usage#766)
- **(complete)** use `type -P` so the CLI-presence guard ignores shell functions by [@&#8203;JamBalaya56562](https://github.com/JamBalaya56562) in [#&#8203;760](jdx/usage#760)
- **(parse)** enforce double\_dash="required" for positional args by [@&#8203;JamBalaya56562](https://github.com/JamBalaya56562) in [#&#8203;762](jdx/usage#762)
- **(windows)** run `run=` scripts with sh when available by [@&#8203;JamBalaya56562](https://github.com/JamBalaya56562) in [#&#8203;765](jdx/usage#765)

##### 🎨 Styling

- fix clippy and deprecation warnings in test and bench targets by [@&#8203;JamBalaya56562](https://github.com/JamBalaya56562) in [#&#8203;763](jdx/usage#763)

### [`v4.1.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#410---2026-07-30)

[Compare Source](jdx/usage@v4.0.0...v4.1.0)

##### 🚀 Features

- **(cli)** declare what each usage command does to the world by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;751](jdx/usage#751)
- **(mcp)** serve a usage spec to an agent over stdio by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;746](jdx/usage#746)
- **(spec)** add a top-level `repository` field by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;747](jdx/usage#747)

##### 🐛 Bug Fixes

- **(parse)** keep a re-declared global's aliases on one flag by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;752](jdx/usage#752)
- complete repeated variadic args by [@&#8203;Jai-JAP](https://github.com/Jai-JAP) in [#&#8203;753](jdx/usage#753)

##### New Contributors

- [@&#8203;Jai-JAP](https://github.com/Jai-JAP) made their first contribution in [#&#8203;753](jdx/usage#753)

### [`v4.0.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#400---2026-07-25)

[Compare Source](jdx/usage@v3.6.0...v4.0.0)

##### 🚀 Features

- **(spec)** allow effect= on flags and args by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;742](jdx/usage#742)

### [`v3.6.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#360---2026-07-25)

[Compare Source](jdx/usage@v3.5.7...v3.6.0)

##### 🚀 Features

- **(spec)** add effect= to declare what a command does to the world by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;739](jdx/usage#739)

##### 🚜 Refactor

- **(spec)** make missed SpecCommand fields a compile error, and fix the four that were already missed by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;740](jdx/usage#740)

### [`v3.5.7`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#357---2026-07-25)

[Compare Source](jdx/usage@v3.5.6...v3.5.7)

##### 🐛 Bug Fixes

- **(parse)** don't leak the mounting CLI's flags into mounted commands; scan past non-global flags by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;738](jdx/usage#738)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yODguMCIsInVwZGF0ZWRJblZlciI6IjQzLjI4OC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiLCJhdXRvbWF0aW9uOmJvdC1hdXRob3JlZCIsImRlcGVuZGVuY3ktdHlwZTo6bWFqb3IiXX0=-->
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