You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
commitbee is designed for interactive terminal use. While the LLM generates a response, its raw JSON is streamed to the terminal token-by-token, spinners and progress lines flow alongside it, and COMMITBEE_LOG=debug adds tracing on top. The final sanitized commit message only appears at the end, on top of all that noise. There is no clean way today to get just the commit message out of commitbee — every piping attempt either leaks noise through or has to silence legitimate error messages with 2>/dev/null.
Concrete use cases that are blocked today:
Piping the generated message into another tool — a validator like commitlint, a preview viewer, a formatter, a custom shell script.
Editor / IDE integrations (VS Code, JetBrains, nvim, etc.) that capture stdout and insert into their commit-message buffer.
Populating the commit-message file from a prepare-commit-msg hook without the current 2>/dev/null workaround that also hides real errors.
Higher-level automation (release-notes pipelines, AI-review chains, etc.) that expects exactly one clean string on stdout.
Example usage once shipped:
# Pipe the generated message through any tool — validator, formatter, preview
commitbee --porcelain | commitlint
commitbee --porcelain | bat -l gitcommit
# Use from any shell script
commitbee --porcelain | your-script.sh
# Populate git's commit-msg file from a prepare-commit-msg hook# ($1 is the path git passes to the hook)
commitbee --porcelain >"$1"
The existing workaround — commitbee --dry-run --yes 2>/dev/null — is undiscoverable, isn't a documented contract, and silences legitimate errors the user would otherwise want to see.
Note on the original phrasing: the live token stream users see today is JSON (the LLM returns a JSON object and commitbee streams it token-by-token before the sanitizer turns it into a Conventional Commit message). The final stdout contract under --porcelain is the sanitized plain-text commit message — the user-facing artifact — not the raw LLM JSON.
Proposed solution
Add a new top-level flag --porcelain that puts commitbee into a machine-readable output mode.
stdout contract:
stdout contains exactly the final sanitized commit message followed by a single \n.
UTF-8, no BOM, no ANSI escape sequences, no terminal hyperlinks.
On any error, stdout is empty; process exits non-zero.
What --porcelain silences:
The live-streamed LLM JSON response (the main source of noise today).
All progress indicators and spinners.
All info / warning / status lines.
All tracing output, regardless of RUST_LOG / COMMITBEE_LOG.
ANSI color in all styled output, including error reports.
Terminal hyperlinks in error reports.
What --porcelain forces non-interactive:
Implies --dry-run — no commit is created; the message is printed and the process exits.
Implies --no-split — split-commit UI is interactive-only.
The Edit / Refine / Cancel review menu is unreachable under --dry-run.
Disables the interactive --allow-secrets confirmation — with --allow-secrets passed, porcelain falls through to the non-interactive "fail closed" branch if secrets are detected.
Incompatibilities (rejected at argument-parse time with exit code 2):
Combination
Reason
--porcelain --yes
--yes commits for real; --porcelain only generates and prints. Mutually exclusive output destinations.
--porcelain --clipboard
stdout contract vs. clipboard destination
--porcelain --show-prompt
swallowing a debug flag silently would be deceptive
--porcelain --verbose
same reasoning
--porcelain -n <N>
multi-candidate UI is pointless without a picker
--porcelain <subcommand>
--porcelain is only meaningful for the default generate flow; it does not apply to config, doctor, completions, hook, init, set-key, get-key, or eval
Errors still flow to stderr (color and hyperlinks disabled under porcelain). The process exits non-zero, so downstream tools detect failure without parsing stdout.
Stability boundary:
The structural contract (one sanitized commit message on stdout, trailing \n, UTF-8, no decoration) is stable from v0.7.0 onward.
The content of the message (wording, capitalization, type/scope choices) depends on the configured LLM and is not stable across prompts, models, or commitbee versions.
Acceptance criteria
commitbee --porcelain writes exactly <message>\n to stdout on a successful generation.
commitbee --porcelain writes zero bytes to stderr on success, regardless of RUST_LOG, COMMITBEE_LOG, or TTY state.
commitbee --porcelain exits 0 on success and non-zero on any failure (no staged changes, LLM unreachable, secrets detected, cancelled, etc.).
commitbee --porcelain combined with any of --yes, --clipboard, --show-prompt, --verbose, -n <N>, or a subcommand exits with an argument-parse error (exit code 2) and empty stdout.
commitbee --porcelain --allow-secrets with staged secrets does not hang on interactive stdin; it fails closed with a non-zero exit and empty stdout.
commitbee --porcelain piped to a non-TTY consumer produces identical output to TTY (no spinner bleed-through, no ANSI).
Integration tests in tests/porcelain.rs cover each of the above using assert_cmd + wiremock + tempfile with env_clear().
A structural lint test walks src/ and fails if println! / print! appears outside a pinned allowlist of known emission sites (prevents future stdout leaks).
Alternatives considered
--dry-run alone: prints the final message to stdout but still streams the live LLM response and progress lines, and can drop into the interactive review menu in a TTY. Not a stable machine-readable contract.
Shell redirection (commitbee --dry-run --yes 2>/dev/null): the current workaround, but silences legitimate errors, is undiscoverable, and isn't a documented contract.
Parsing or grepping commitbee's current stdout: fragile; breaks whenever output formatting changes.
--just-body (original suggestion): ambiguous — a Conventional Commit message has both a subject and a body, so "just body" reads as "only the body paragraph, omitting the subject." Rejected.
--format=raw|json as a single knob: semantically awkward because --format=raw implies a format is being chosen when the intent is the absence of formatting. Structured JSON output is a separate future concern — see Related.
Area
CLI / UX
Related
Future structured output: a separate --format=<json|…> modifier is expected to land when structured JSON output becomes a feature. It will be orthogonal to --porcelain — one controls stdout cleanliness, the other controls output shape. --porcelain alone will always mean "plain-text commit message."
Milestone: v0.7.0
Issue originally reported by @yegor256; description refined by the maintainer.
Problem or motivation
commitbee is designed for interactive terminal use. While the LLM generates a response, its raw JSON is streamed to the terminal token-by-token, spinners and progress lines flow alongside it, and
COMMITBEE_LOG=debugadds tracing on top. The final sanitized commit message only appears at the end, on top of all that noise. There is no clean way today to get just the commit message out of commitbee — every piping attempt either leaks noise through or has to silence legitimate error messages with2>/dev/null.Concrete use cases that are blocked today:
commitlint, a preview viewer, a formatter, a custom shell script.prepare-commit-msghook without the current2>/dev/nullworkaround that also hides real errors.Example usage once shipped:
The existing workaround —
commitbee --dry-run --yes 2>/dev/null— is undiscoverable, isn't a documented contract, and silences legitimate errors the user would otherwise want to see.Proposed solution
Add a new top-level flag
--porcelainthat puts commitbee into a machine-readable output mode.stdout contract:
\n.What
--porcelainsilences:RUST_LOG/COMMITBEE_LOG.What
--porcelainforces non-interactive:--dry-run— no commit is created; the message is printed and the process exits.--no-split— split-commit UI is interactive-only.--dry-run.--allow-secretsconfirmation — with--allow-secretspassed, porcelain falls through to the non-interactive "fail closed" branch if secrets are detected.Incompatibilities (rejected at argument-parse time with exit code 2):
--porcelain --yes--yescommits for real;--porcelainonly generates and prints. Mutually exclusive output destinations.--porcelain --clipboard--porcelain --show-prompt--porcelain --verbose--porcelain -n <N>--porcelain <subcommand>--porcelainis only meaningful for the default generate flow; it does not apply toconfig,doctor,completions,hook,init,set-key,get-key, orevalErrors still flow to stderr (color and hyperlinks disabled under porcelain). The process exits non-zero, so downstream tools detect failure without parsing stdout.
Stability boundary:
\n, UTF-8, no decoration) is stable from v0.7.0 onward.Acceptance criteria
commitbee --porcelainwrites exactly<message>\nto stdout on a successful generation.commitbee --porcelainwrites zero bytes to stderr on success, regardless ofRUST_LOG,COMMITBEE_LOG, or TTY state.commitbee --porcelainexits 0 on success and non-zero on any failure (no staged changes, LLM unreachable, secrets detected, cancelled, etc.).commitbee --porcelaincombined with any of--yes,--clipboard,--show-prompt,--verbose,-n <N>, or a subcommand exits with an argument-parse error (exit code 2) and empty stdout.commitbee --porcelain --allow-secretswith staged secrets does not hang on interactive stdin; it fails closed with a non-zero exit and empty stdout.commitbee --porcelainpiped to a non-TTY consumer produces identical output to TTY (no spinner bleed-through, no ANSI).tests/porcelain.rscover each of the above usingassert_cmd+wiremock+tempfilewithenv_clear().src/and fails ifprintln!/print!appears outside a pinned allowlist of known emission sites (prevents future stdout leaks).Alternatives considered
--dry-runalone: prints the final message to stdout but still streams the live LLM response and progress lines, and can drop into the interactive review menu in a TTY. Not a stable machine-readable contract.commitbee --dry-run --yes 2>/dev/null): the current workaround, but silences legitimate errors, is undiscoverable, and isn't a documented contract.--just-body(original suggestion): ambiguous — a Conventional Commit message has both a subject and a body, so "just body" reads as "only the body paragraph, omitting the subject." Rejected.--format=raw|jsonas a single knob: semantically awkward because--format=rawimplies a format is being chosen when the intent is the absence of formatting. Structured JSON output is a separate future concern — see Related.Area
CLI / UX
Related
--format=<json|…>modifier is expected to land when structured JSON output becomes a feature. It will be orthogonal to--porcelain— one controls stdout cleanliness, the other controls output shape.--porcelainalone will always mean "plain-text commit message."Issue originally reported by @yegor256; description refined by the maintainer.