No -F/--body-file for body input on any issue/pr/release/milestone command (gh-parity scripting gap) #124

Open
opened 2026-06-11 00:47:52 +00:00 by stephen · 1 comment
Owner

Observation

Every fj command that takes a body accepts inline --body <text>, --body - (stdin), or $EDITOR, but none accept -F/--body-file <path>. gh's -F/--body-file is the standard way scripts and agents pass a generated body, and its absence forces a stdin-redirect idiom that does not compose with fj's own editor fallback.

The pattern is uniform across the whole body-accepting surface. Each body: Option<String> is documented identically ("Use - to read from stdin. Omit to open $EDITOR") and there is no file variant:

  • fj issue create / edit / comment (src/cli/issue.rs:120-122, :155; src/cli/issue_comment.rs:21-23)
  • fj pr create / edit / comment (src/cli/pr.rs:120, :143, :239)
  • fj release create / edit (src/cli/release.rs:78-80, :104-106)
  • fj milestone create / edit (src/cli/milestone.rs:68-70, :85-87)
$ fj issue create --help | grep -iE 'body|file'
  -b, --body <BODY>  Body. Use `-` to read from stdin. Omit to open `$EDITOR`

The shared resolvers editor::read_body / resolve_body (src/cli/editor.rs:68, :104) only understand Some("-") -> stdin, Some(s) -> literal, None -> editor. There is no "read this path" arm.

Why it matters

The body-file flag is the workhorse of scripted issue/PR/release authoring, and gh's docs and examples lean on it everywhere (gh pr create -F body.md, gh release create -F notes.md). An agent that generates a multi-paragraph markdown body almost always writes it to a temp file first, then references that file; -F notes.md is the muscle-memory call.

fj's only equivalent is shell redirection: fj issue create -t "..." --body - < body.md. That works, but:

  • It is strictly less ergonomic and breaks gh parity for anyone porting scripts (-F is what the script already says).
  • Stdin is a single shared channel, so the --body - idiom cannot coexist with anything else that wants stdin in the same pipeline, whereas a file path is positional and unambiguous.
  • It is easy to get subtly wrong (forgetting --body -, so fj silently opens $EDITOR and hangs a non-interactive job, since None means "open editor").

That last point is the sharp edge for automation: omitting the body to a tool that defaults to launching $EDITOR is a hang in CI, where -F path would be an unambiguous, non-interactive read.

Possible directions (sketches)

  • (sketch) Add -F/--body-file <PATH> to every command that has --body, resolved in the shared editor::read_body / resolve_body helpers (src/cli/editor.rs:68-104) so it lands once for all call sites. Treat --body-file - as stdin (matching gh, which accepts -F -), keep --body/--body-file mutually exclusive via clap.
  • (sketch) While there, consider a --body-file arm that, when the path is missing, errors instead of falling through to $EDITOR, preserving the "never block in CI" property.
## Observation Every `fj` command that takes a body accepts inline `--body <text>`, `--body -` (stdin), or `$EDITOR`, but none accept `-F/--body-file <path>`. `gh`'s `-F/--body-file` is the standard way scripts and agents pass a generated body, and its absence forces a stdin-redirect idiom that does not compose with `fj`'s own editor fallback. The pattern is uniform across the whole body-accepting surface. Each `body: Option<String>` is documented identically ("Use `-` to read from stdin. Omit to open `$EDITOR`") and there is no file variant: - `fj issue create` / `edit` / `comment` (`src/cli/issue.rs:120-122`, `:155`; `src/cli/issue_comment.rs:21-23`) - `fj pr create` / `edit` / `comment` (`src/cli/pr.rs:120`, `:143`, `:239`) - `fj release create` / `edit` (`src/cli/release.rs:78-80`, `:104-106`) - `fj milestone create` / `edit` (`src/cli/milestone.rs:68-70`, `:85-87`) ``` $ fj issue create --help | grep -iE 'body|file' -b, --body <BODY> Body. Use `-` to read from stdin. Omit to open `$EDITOR` ``` The shared resolvers `editor::read_body` / `resolve_body` (`src/cli/editor.rs:68`, `:104`) only understand `Some("-")` -> stdin, `Some(s)` -> literal, `None` -> editor. There is no "read this path" arm. ## Why it matters The body-file flag is the workhorse of scripted issue/PR/release authoring, and `gh`'s docs and examples lean on it everywhere (`gh pr create -F body.md`, `gh release create -F notes.md`). An agent that *generates* a multi-paragraph markdown body almost always writes it to a temp file first, then references that file; `-F notes.md` is the muscle-memory call. `fj`'s only equivalent is shell redirection: `fj issue create -t "..." --body - < body.md`. That works, but: - It is strictly less ergonomic and breaks `gh` parity for anyone porting scripts (`-F` is what the script already says). - Stdin is a single shared channel, so the `--body -` idiom cannot coexist with anything else that wants stdin in the same pipeline, whereas a file path is positional and unambiguous. - It is easy to get subtly wrong (forgetting `--body -`, so `fj` silently opens `$EDITOR` and hangs a non-interactive job, since `None` means "open editor"). That last point is the sharp edge for automation: omitting the body to a tool that defaults to launching `$EDITOR` is a hang in CI, where `-F path` would be an unambiguous, non-interactive read. ## Possible directions (sketches) - *(sketch)* Add `-F/--body-file <PATH>` to every command that has `--body`, resolved in the shared `editor::read_body` / `resolve_body` helpers (`src/cli/editor.rs:68-104`) so it lands once for all call sites. Treat `--body-file -` as stdin (matching `gh`, which accepts `-F -`), keep `--body`/`--body-file` mutually exclusive via clap. - *(sketch)* While there, consider a `--body-file` arm that, when the path is missing, errors instead of falling through to `$EDITOR`, preserving the "never block in CI" property.
Author
Owner

Converted to backlog item rasterstate/fj#140 (p1, size M).

Converted to backlog item rasterstate/fj#140 (p1, size M).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rasterstate/fj#124
No description provided.