Skip to content

Explicit --output: always regenerate, infer format from suffix, fix stdout hang (#221, #222, #223 part 1)#237

Merged
cboos merged 2 commits into
mainfrom
dev/output-explicit-o
Jun 24, 2026
Merged

Explicit --output: always regenerate, infer format from suffix, fix stdout hang (#221, #222, #223 part 1)#237
cboos merged 2 commits into
mainfrom
dev/output-explicit-o

Conversation

@cboos

@cboos cboos commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

First of a small series addressing the --output / --format / stdout group of issues (#220#223). Three related fixes around how an explicit --output destination is handled.

#221 — existing -o file silently kept stale content

The single-file regeneration gate only compared the embedded <!-- Generated by claude-code-log vX --> marker — it had no notion of which source produced the file. Converting a different transcript into an existing same-version path was skipped while the CLI still printed Successfully converted.

convert_jsonl_to gains a force_regenerate flag (first in the should_regenerate or, and gating the directory-mode early exit); the CLI sets it for an explicit -o file destination. Scoped to files because directory inputs already track source staleness via the cache (is_html_stale) — a different transcript written to the same directory regenerates correctly without forcing — and --all-projects converts with output=None, so its incremental skip is never forced. This also aligns convert_jsonl_to with the --session-id path, which already always overwrites.

#222 — format ignored the --output suffix

-o foo.md was routed as a file but still rendered HTML. When -f is omitted, the format is now inferred from a recognised --output suffix (.md/.markdown → markdown, .html, .json). An explicit conflict (-o foo.md -f html) is a clear UsageError (before any write) instead of mismatched content. Adds utils.format_from_output_suffix; uses the Click parameter source to detect an omitted -f.

#223 (part 1) — -o /dev/stdout hung

is_outdated / check_html_version opened the path read-only and readline()'d to sniff the version; on a pipe that deadlocks. They now guard on Path.is_file() (not exists()), so a non-regular destination is treated as outdated/no-version without being opened. (Part 2 — routing status to stderr for clean stdout streaming — follows in a separate PR.)

Tests

test/test_output_explicit.py (11 cases): stale-overwrite for file output, the directory-input cross-source case (handled by the cache), the three suffix inferences, matching-format OK, conflict error (asserts nothing written), and FIFO no-hang for all three renderers. just ci green; no snapshot churn.

Closes #221
Closes #222

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • When you provide an explicit output path, the export format can be inferred from the filename suffix (HTML/Markdown/JSON) if --format is omitted.
  • Bug Fixes

    • Exporting to a specific file path now regenerates as needed, preventing stale results.
    • Conflicts between --format and the output filename suffix now fail with a nonzero exit.
    • Improved handling of special destinations (pipes/stdout/FIFOs) to avoid hangs during version checks.
  • Tests

    • Added coverage for explicit output, format inference/conflicts, and non-regular destination behavior.
  • Documentation

    • Clarified outdated-check behavior for non-regular destinations and explicit --output.

Three related fixes to how an explicit --output destination is handled.

#221 — stale content silently kept. The single-file regeneration gate
only compared the embedded `Generated by claude-code-log vX` marker, with
no notion of which source produced the file, so converting a *different*
transcript into an existing same-version path was skipped while the CLI
still reported success. convert_jsonl_to gains a `force_regenerate` flag
(first in the should_regenerate `or`, and gating the directory-mode early
exit); the CLI sets it for an explicit `-o` *file* destination. Scoped to
files because directory inputs already track source staleness via the
cache (is_html_stale) — a different transcript to the same dir regenerates
correctly without forcing — and `--all-projects` converts with
output=None, so its incremental skip is never forced. This aligns
convert_jsonl_to with the --session-id path, which already always
overwrites.

#222 — format ignored the suffix. `-o foo.md` was routed as a file but
still rendered HTML. When `-f` is omitted, the format is now inferred from
a recognised --output suffix (.md/.markdown -> markdown, .html, .json);
an explicit conflict (`-o foo.md -f html`) is a clear UsageError instead
of mismatched content. Adds utils.format_from_output_suffix and uses the
Click parameter source to detect an omitted -f.

#223 part 1 — `-o /dev/stdout` hung. is_outdated / check_html_version
opened the path read-only and readline()'d to sniff the version; on a
pipe that deadlocks. They now guard on Path.is_file() (not exists()), so
a non-regular destination is treated as outdated/no-version without being
opened. (Part 2 — routing status to stderr for clean stdout streaming —
follows separately.)

Tests in test/test_output_explicit.py cover all three (incl. the
directory-input cross-source case handled by the cache). Plan for the
wider --output/--format/TUI set in work/tui-output-fixes.md.

Closes #221
Closes #222

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 78f525d5-3b4a-4106-a8e4-9da6e5f92778

📥 Commits

Reviewing files that changed from the base of the PR and between 3365fb7 and 182d0a2.

📒 Files selected for processing (2)
  • test/test_output_explicit.py
  • work/tui-output-fixes.md
✅ Files skipped from review due to trivial changes (1)
  • work/tui-output-fixes.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/test_output_explicit.py

📝 Walkthrough

Walkthrough

The PR adds suffix-based output-format inference, forces regeneration for explicit file outputs, and changes renderer version checks to treat non-regular destinations as outdated or versionless. Tests and docs cover the new output behavior and pipe-safe handling.

Changes

Output handling changes

Layer / File(s) Summary
Suffix inference and validation
claude_code_log/utils.py, claude_code_log/cli.py, test/test_output_explicit.py
Suffixes now map to canonical formats, --output can determine the format when --format is omitted, and tests cover matching and conflicting suffix cases.
Explicit file regeneration
claude_code_log/converter.py, claude_code_log/cli.py, dev-docs/application_model.md, test/test_output_explicit.py
Explicit file outputs pass force_regenerate into conversion, the converter honors that override in cached and uncached paths, and tests cover overwrite behavior.
Non-regular destination guards
claude_code_log/html/renderer.py, claude_code_log/json/renderer.py, claude_code_log/markdown/renderer.py, test/test_output_explicit.py
Markdown, JSON, and HTML version checks switch to Path.is_file() for the initial guard, and tests cover FIFO and missing-path cases.
Investigation notes and rollout plan
work/tui-output-fixes.md
The notes file records verified findings, extra findings, and the staged rollout plan for the output fixes.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant cli as "claude_code_log.cli.main"
  participant suffix as "claude_code_log.utils.format_from_output_suffix"
  participant conv as "claude_code_log.converter.convert_jsonl_to"
  participant renderer as "renderer.is_outdated / check_html_version"
  User->>cli: run with --output out.md
  cli->>suffix: infer format from suffix
  alt explicit file output
    cli->>conv: call with force_regenerate=True
    conv->>renderer: skip stale-file shortcut for explicit output
    conv-->>User: write regenerated output
  else conflict with --format
    cli-->>User: raise click.UsageError
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • daaain/claude-code-log#36: Updates the same renderer version-sniff path by switching JsonRenderer.is_outdated() and related guards to Path.is_file().

Poem

A rabbit hopped through suffixes bright,
and found the right format by moonlight.
No stale carrots stayed, no pipes got stuck,
just fresh logs and a little luck.
Thump! 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The stdout/non-regular-path hang fix and related tests/docs go beyond #221 and #222, which only cover regeneration and format inference. Move the stdout-hang hardening into a separate PR or link the related issue, and keep this PR scoped to #221 and #222.
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: explicit output regeneration, suffix-based format inference, and the stdout hang fix.
Linked Issues check ✅ Passed The changes satisfy #221 and #222 by forcing regeneration for explicit file outputs and inferring or validating format from the output suffix.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev/output-explicit-o

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 markdownlint-cli2 (0.22.1)
work/tui-output-fixes.md

markdownlint-cli2 v0.22.1 (markdownlint v0.40.0)
Error: Unable to use configuration file '/coderabbit-0.markdownlint-cli2.jsonc'; ENOENT: no such file or directory, open '/coderabbit-0.markdownlint-cli2.jsonc'
at throwForConfigurationFile (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:48:9)
at readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:169:5)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
[cause]: Error: ENOENT: no such file or directory, open '/coderabbit-0.markdownlint-cli2.jsonc'
at async open (node:internal/fs/promises:640:25)
at async Object.readFile (node:internal/fs/promises:1287:14)
at async readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:141:17)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/coderabbit-0.markdownlint-cli2.jsonc'
}
}


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with 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.

Inline comments:
In `@test/test_output_explicit.py`:
- Around line 71-93: The test currently uses a file-like output path, so it
exercises the file-output regeneration path instead of the directory-output
behavior described in the test name/docstring. Update the test in
test_directory_input_cross_source_still_regenerates to use a suffixless output
destination for -o so force_regenerate is not triggered, or else adjust the
docstring to explicitly describe the file-output case; keep the assertions about
ALPHA_dir_source and BETA_dir_source matching the intended directory-input
scenario.

In `@work/tui-output-fixes.md`:
- Around line 14-15: Update the `#223` hang-fix note to reflect only the remaining
HTML version-sniffing path, since markdown/renderer.py and json/renderer.py
already use Path.is_file() in is_outdated(); adjust the plan wording to point at
html/renderer.py’s is_outdated() logic and keep the scope narrow so it no longer
claims those other renderers still need the guard.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 759777b5-c4d2-44a9-b932-3e0ab3e1f8cf

📥 Commits

Reviewing files that changed from the base of the PR and between 61fd066 and 3365fb7.

📒 Files selected for processing (9)
  • claude_code_log/cli.py
  • claude_code_log/converter.py
  • claude_code_log/html/renderer.py
  • claude_code_log/json/renderer.py
  • claude_code_log/markdown/renderer.py
  • claude_code_log/utils.py
  • dev-docs/application_model.md
  • test/test_output_explicit.py
  • work/tui-output-fixes.md

Comment thread test/test_output_explicit.py Outdated
Comment thread work/tui-output-fixes.md
- os.mkfifo is POSIX-only, so the three FIFO-based is_outdated/no-hang
  tests AttributeError'd on windows-latest. Guard them with skipif and add
  cross-platform directory-based tests (a directory is also not a regular
  file, so it exercises the same Path.is_file() guard without a FIFO).
- Rename/retarget the directory-input regeneration test: it uses an
  explicit `-o combined.html` (a *file* destination), so force_regenerate
  is set — the test exercises the directory-input → file-output #221 path,
  not a directory-output path. Docstring corrected to match (CodeRabbit).
- Clarify work/tui-output-fixes.md "Verified findings" are the pre-fix
  state each PR then fixes (CodeRabbit read them as current).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@cboos cboos merged commit c4fb92f into main Jun 24, 2026
17 checks passed
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.

Infer output format from --output file extension --output to an existing file silently skips regeneration and keeps stale content

1 participant