Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions claude_code_log/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -904,11 +904,28 @@ def main(
err=True,
)

# `--output` / `--format` are no-ops under --tui: the TUI's export
# actions write to a fixed per-session path and run_session_browser
# never receives these flags (issue #220). Warn rather than silently
# ignore, mirroring the --expand-paths / --filter-path cases above.
if tui and (
output is not None
or ctx.get_parameter_source("output_format")
is not click.core.ParameterSource.DEFAULT
):
click.echo(
"Warning: --output / --format are ignored in --tui mode; "
"use the TUI's in-app export actions instead.",
err=True,
)

# Infer --format from an explicit --output file suffix when -f was not
# given; error on an explicit conflict like `-o foo.md -f html` rather
# than writing mismatched content (issue #222). `.md`/`.markdown` both
# imply the canonical `markdown` format.
if output is not None and _output_path_is_file(output):
# imply the canonical `markdown` format. Skipped under --tui: both flags
# are no-ops there (warned above), so erroring on their conflict would
# contradict the warning and block the TUI from launching (#220).
if not tui and output is not None and _output_path_is_file(output):
from .utils import format_from_output_suffix

suffix_format = format_from_output_suffix(output)
Expand Down
91 changes: 91 additions & 0 deletions test/test_tui_output_warn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""PR 3 of the #220-223 set (issue #220): warn that --output / --format
are no-ops under --tui.

The TUI ignores --output/--format (run_session_browser never receives them;
its export actions write to a fixed per-session path). Rather than silently
ignoring, the CLI now warns — mirroring the --expand-paths/--filter-path
ignored-flag warnings. These tests drive the warn via an empty projects
dir so the TUI returns early ("No projects ...") without launching Textual.
"""

from pathlib import Path

from click.testing import CliRunner

from claude_code_log.cli import main


def _empty_projects(tmp_path: Path) -> Path:
d = tmp_path / "projects"
d.mkdir()
return d


class TestTuiOutputWarn:
def setup_method(self):
self.runner = CliRunner()

def test_warns_on_output_under_tui(self, tmp_path: Path):
r = self.runner.invoke(
main,
["--tui", "--projects-dir", str(_empty_projects(tmp_path)), "-o", "x.md"],
)
assert r.exit_code == 0, r.output
assert "ignored in --tui mode" in r.stderr

def test_warns_on_explicit_format_under_tui(self, tmp_path: Path):
r = self.runner.invoke(
main,
[
"--tui",
"--projects-dir",
str(_empty_projects(tmp_path)),
"-f",
"markdown",
],
)
assert r.exit_code == 0, r.output
assert "ignored in --tui mode" in r.stderr

def test_no_warn_under_tui_without_output_or_format(self, tmp_path: Path):
r = self.runner.invoke(
main, ["--tui", "--projects-dir", str(_empty_projects(tmp_path))]
)
assert r.exit_code == 0, r.output
assert "ignored in --tui mode" not in r.stderr

def test_warn_goes_to_stderr_not_stdout(self, tmp_path: Path):
r = self.runner.invoke(
main,
["--tui", "--projects-dir", str(_empty_projects(tmp_path)), "-o", "x.md"],
)
assert "ignored in --tui mode" not in r.stdout
assert "ignored in --tui mode" in r.stderr

def test_conflicting_output_format_under_tui_warns_not_errors(self, tmp_path: Path):
"""A `-o x.md -f html` conflict (#222) must NOT hard-error under --tui:
both flags are ignored there, so we warn and let the TUI proceed
rather than blocking on a conflict the user can't act on (#220)."""
r = self.runner.invoke(
main,
[
"--tui",
"--projects-dir",
str(_empty_projects(tmp_path)),
"-o",
"x.md",
"-f",
"html",
],
)
assert r.exit_code == 0, r.output
assert "ignored in --tui mode" in r.stderr
assert "conflicts" not in r.output

def test_no_warn_when_not_tui(self, tmp_path: Path):
"""A normal (non-TUI) `-o` conversion must not emit the TUI warning."""
src = tmp_path / "s.jsonl"
src.write_text("", encoding="utf-8") # empty → no messages, still converts
out = tmp_path / "out.md"
r = self.runner.invoke(main, [str(src), "-o", str(out)])
assert "ignored in --tui mode" not in r.output
Comment on lines +90 to +91

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add an exit-code assertion in the non-TUI baseline test.

Line 90 should also assert r.exit_code == 0; otherwise this test can pass even when invocation fails for unrelated reasons.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/test_tui_output_warn.py` around lines 90 - 91, The non-TUI baseline test
using self.runner.invoke(main, [str(src), "-o", str(out)]) only checks output
and can miss failures, so update this test to also assert r.exit_code == 0 after
invoking main; keep the existing output assertion alongside it so the test
validates both successful execution and the expected TUI-related message
behavior.

Loading