Skip to content

Feat/cli export exit codes#56

Closed
clean6378-max-it wants to merge 4 commits into
masterfrom
feat/cli-export-exit-codes
Closed

Feat/cli export exit codes#56
clean6378-max-it wants to merge 4 commits into
masterfrom
feat/cli-export-exit-codes

Conversation

@clean6378-max-it
Copy link
Copy Markdown
Collaborator

@clean6378-max-it clean6378-max-it commented May 28, 2026

closes #53

Summary

Mechanical split of the utils/jsonl_parser.py monolith (~762 lines) into focused modules with no behavior change. The 15-predicate tool-result dispatch table is isolated in utils/tool_dispatch.py with an explicit ordering contract. quick_session_info moves to utils/session_peek.py; shared content helpers move to utils/jsonl_helpers.py. All existing imports from utils.jsonl_parser continue to work via re-exports. No test file changes.

Changes

  • utils/tool_dispatch.py_TOOL_RESULT_DISPATCH, all _tool_result_pred_* / _tool_result_build_*, _parse_tool_result
  • utils/session_peek.pyquick_session_info() two-pass metadata peek
  • utils/jsonl_helpers.py_entry_message, _normalize_content, _extract_text, _extract_images, _infer_title, _strip_system_tags
  • utils/jsonl_parser.py (slimmed) — parse_session(), message processors, _track_file_activity, re-exports
  • docs/architecture.md — one-line update: dispatch table lives in utils/tool_dispatch.py

Dispatch predicate order is unchanged (first match wins; task_message before narrower task arms).

Backward compatibility

Existing imports unchanged:

  • from utils.jsonl_parser import parse_session
  • from utils.jsonl_parser import quick_session_info
  • from utils.jsonl_parser import _parse_tool_result, _TOOL_RESULT_DISPATCH
  • from utils.jsonl_parser import _strip_system_tags

Test plan

  • pytest tests/test_jsonl_parser.py tests/test_real_session_fixtures.py tests/test_jsonl_validation.py tests/test_null_usage_tokens.py -v
  • pytest -q (317 tests)
  • mypy -p api -p utils -p models
  • Import smoke: parse_session, quick_session_info, _parse_tool_result, _TOOL_RESULT_DISPATCH from utils.jsonl_parser
  • Tuesday dispatch-order regression (test_real_session_fixtures.py) passes without modification

Out of scope

  • No dispatch reordering or new tool-result types
  • No runtime validation changes
  • No export engine changes

Summary by CodeRabbit

Release Notes

  • Documentation
    • Clarified exit code behavior in export command documentation. Exit codes 0/1/2 now explicitly apply only to bulk exports; single-session exports may return different exit codes.

Review Change Stack

@clean6378-max-it clean6378-max-it self-assigned this May 28, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c5e39b77-751f-4fd7-bef4-0e60371128d8

📥 Commits

Reviewing files that changed from the base of the PR and between 3898453 and b022a70.

📒 Files selected for processing (1)
  • scripts/export.py
✅ Files skipped from review due to trivial changes (1)
  • scripts/export.py

📝 Walkthrough

Walkthrough

The export subcommand's module docstring is clarified to document that exit codes 0/1/2 apply only to bulk-export paths, while single-session exports may exit non-zero when internal errors occur.

Changes

CLI Exit-Code Documentation Clarification

Layer / File(s) Summary
Exit-code scope clarification
scripts/export.py
Module docstring updated to clarify that the documented 0/1/2 exit-code semantics apply only to bulk-export mode; single-session context may exit non-zero.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~2 minutes

Possibly related PRs

Suggested reviewers

  • timon0305
  • wpak-ai

Poem

🐰 A docstring whispers from export's way,
"Exit codes dance in bulk-export's day,
Single sessions? They roam wild and free—
Non-zero escapes are their destiny!"

🚥 Pre-merge checks | ✅ 1 | ❌ 4

❌ Failed checks (4 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title mentions 'cli export exit codes' but the PR summary describes a large refactoring of jsonl_parser.py into focused modules. Update the title to reflect the main change: 'Refactor jsonl_parser.py monolith into focused modules' or 'Extract JSONL parser into tool_dispatch, session_peek, and helpers modules'.
Linked Issues check ⚠️ Warning The raw summary describes changes to scripts/export.py documentation, but linked issue #53 requires splitting utils/jsonl_parser.py into multiple focused modules. Verify that all required modules (tool_dispatch.py, session_peek.py, jsonl_helpers.py, slimmed jsonl_parser.py) are created and re-exports are maintained as specified in issue #53.
Out of Scope Changes check ⚠️ Warning The raw summary indicates only scripts/export.py was modified, which is unrelated to the jsonl_parser refactoring requirements in issue #53. Ensure all changes in this PR are focused on splitting jsonl_parser.py and updating imports; exclude unrelated CLI export documentation changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 feat/cli-export-exit-codes

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@scripts/export.py`:
- Line 18: Change the misleading comment that states "--session single-export
always exits 0" to clarify that the 0/1/2 exit-code mapping applies only to bulk
export; mention that single-session exports (handled by cmd_export) may still
exit non-zero (e.g., via _die("Session not found...") which returns 1). Update
the parenthetical text to something like "0/1/2 mapping applies only to bulk
export; single-session exports may exit 0 or non-zero (e.g., cmd_export can call
_die)."
🪄 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: c0e7937c-7a4b-4df4-8f59-c9dd37da2dab

📥 Commits

Reviewing files that changed from the base of the PR and between 75b8174 and fa8564b.

📒 Files selected for processing (3)
  • README.md
  • scripts/export.py
  • tests/test_cli_export_exit_codes.py

Comment thread scripts/export.py Outdated
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

Only print the Exported N of M summary when exported_session_count or
failure_count is non-zero, so clean --since incremental cron runs with
unchanged mtimes stay silent on stderr. Add subprocess test for the
no-op path; tighten since-last early-return test to realistic 0/0 counts.
…mary

- m = n + k (exported + failed) instead of total_candidates; avoids
  misleading counts for incremental no-ops and --since last date-filtered
  sessions
- Use elif so mutual exclusion of exit-1/exit-2 is explicit
- Route summary to stdout when k==0 (clean success), stderr when k>0;
  prevents cron monitors treating clean exports as failures
- Add test_since_last_early_return_exits_one_on_failure: validates
  actual exit code 1 without mocking _exit_bulk_export
- Use explicit base/test-project instead of next(base.iterdir())
- Add comment on sys.exit branches and --session doc note in docstring
@clean6378-max-it clean6378-max-it force-pushed the feat/cli-export-exit-codes branch from 3898453 to b022a70 Compare May 28, 2026 16:29
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

@clean6378-max-it
Copy link
Copy Markdown
Collaborator Author

@CodeRabbit, I need you to reeview this PR in more details

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== utils/tool_dispatch.py ==="
cat utils/tool_dispatch.py 2>/dev/null || echo "FILE NOT FOUND"

echo ""
echo "=== utils/session_peek.py ==="
cat utils/session_peek.py 2>/dev/null || echo "FILE NOT FOUND"

echo ""
echo "=== utils/jsonl_helpers.py ==="
cat utils/jsonl_helpers.py 2>/dev/null || echo "FILE NOT FOUND"

echo ""
echo "=== utils/jsonl_parser.py ==="
cat utils/jsonl_parser.py 2>/dev/null || echo "FILE NOT FOUND"

echo ""
echo "=== scripts/export.py ==="
cat scripts/export.py 2>/dev/null || echo "FILE NOT FOUND"

echo ""
echo "=== docs/architecture.md ==="
cat docs/architecture.md 2>/dev/null || echo "FILE NOT FOUND"

echo ""
echo "=== utils/__init__.py ==="
cat utils/__init__.py 2>/dev/null || echo "FILE NOT FOUND"

echo ""
echo "=== All files in utils/ ==="
ls -la utils/ 2>/dev/null || echo "DIR NOT FOUND"

echo ""
echo "=== Check for circular import risks ==="
rg -n "^from utils\." utils/ --include="*.py" 2>/dev/null
rg -n "^import utils\." utils/ --include="*.py" 2>/dev/null

echo ""
echo "=== Check re-exports in jsonl_parser.py ==="
rg -n "^from .* import|^import " utils/jsonl_parser.py 2>/dev/null

echo ""
echo "=== Check all imports of jsonl_parser symbols from elsewhere ==="
rg -rn "from utils.jsonl_parser import|from utils import.*parse_session\|quick_session_info\|_parse_tool_result\|_TOOL_RESULT_DISPATCH\|_strip_system_tags" --include="*.py" 2>/dev/null

echo ""
echo "=== Check tests directory ==="
ls tests/ 2>/dev/null || ls test/ 2>/dev/null || echo "No tests dir found"
find . -name "test_*.py" -o -name "*_test.py" 2>/dev/null | head -20

echo ""
echo "=== Check for any TODO / FIXME in new files ==="
rg -n "TODO|FIXME|HACK|XXX" utils/tool_dispatch.py utils/session_peek.py utils/jsonl_helpers.py 2>/dev/null

echo ""
echo "=== Check type annotations in new modules ==="
rg -n "def " utils/tool_dispatch.py utils/session_peek.py utils/jsonl_helpers.py 2>/dev/null

echo ""
echo "=== Check _TOOL_RESULT_DISPATCH structure ==="
grep -A 60 "_TOOL_RESULT_DISPATCH" utils/tool_dispatch.py 2>/dev/null | head -80

echo ""
echo "=== PR diff summary ==="
gh pr view 56 --json files,title,body,additions,deletions 2>/dev/null || echo "gh not available"

Length of output: 71654



@clean6378-max-it Here is a detailed review of PR #56.


Detailed Review

⚠️ Critical: PR Description Does Not Match the Actual Diff

The PR description and the linked issue (#53) describe a substantial refactoring — splitting utils/jsonl_parser.py into three new modules (utils/tool_dispatch.py, utils/session_peek.py, utils/jsonl_helpers.py). None of those files exist on this branch.

=== utils/tool_dispatch.py ===
FILE NOT FOUND

=== utils/session_peek.py ===
FILE NOT FOUND

=== utils/jsonl_helpers.py ===
FILE NOT FOUND

The only actual change in this PR is 2 lines added / 1 deleted in scripts/export.py — a docstring clarification. The utils/jsonl_parser.py is still the full 28 KB monolith. Either the refactoring commits were not pushed, or the PR description is describing future/planned work that isn't in this branch.


Actual Change — scripts/export.py (docstring only)

The real change adds this parenthetical to the module-level docstring:

Exit codes (export subcommand):
  0all sessions exported successfully (or nothing to export, no errors)
  1total failure (no sessions exported; one or more errors)
  2partial failure (some sessions exported, some failed)
  (0/1/2 mapping applies only to bulk export; single-session exports may exit
  0 or non-zero, e.g. cmd_export can call _die and exit 1)

Assessment: The clarification is accurate. Looking at cmd_export:

  • Single-session path calls _die() (always exits 1) for errors like "session not found"
  • Single-session path calls _die() before _exit_bulk_export is ever reached
  • There is no _exit_bulk_export call on the single-session path

The docs are correct, and the clarification is useful. ✅


Issues Found in utils/jsonl_parser.py (the un-split monolith)

1. re imported inside function body

def _strip_system_tags(text: str) -> str:
    import re   # ← line ~470, inside function
    ...

re should be at the top of the file alongside the other stdlib imports. Importing inside a function is a minor performance hit on every call (though re is cached by Python's import machinery, it still does a dict lookup per call).

2. Broad task_message predicate shadows narrower task predicates

def _tool_result_pred_task_message(tr: dict[str, Any]) -> bool:
    return "task_id" in tr or "message" in tr  # ← very broad

# Sits BEFORE all three of these:
def _tool_result_pred_task_retrieval(tr: dict[str, Any]) -> bool:
    return "retrieval_status" in tr and "task" in tr

def _tool_result_pred_task_completed(tr: dict[str, Any]) -> bool:
    return "agentId" in tr and "totalDurationMs" in tr

def _tool_result_pred_task_async(tr: dict[str, Any]) -> bool:
    return "agentId" in tr and "isAsync" in tr

Any task_retrieval, task_completed, or task_async payload that also happens to include a "message" key will be misclassified as task_message. The comment acknowledges this and defers to a follow-up, but it's worth flagging as a latent correctness risk.

Suggested fix: Tighten task_message to use AND logic (require both keys together) or move it after the three more-specific predicates:

def _tool_result_pred_task_message(tr: dict[str, Any]) -> bool:
    # Require both task_id and message to avoid shadowing narrower predicates
    return "task_id" in tr and "message" in tr

This would need validation against existing fixtures before merging.

3. Off-by-one in quick_session_info line limit

for line in f:
    lines_read += 1
    if lines_read > 80:   # breaks AFTER reading line 81
        break

The docstring says "first ~50 lines" but the limit is effectively 81 lines. Consider making the constant explicit and consistent:

MAX_PEEK_LINES = 80
...
if lines_read > MAX_PEEK_LINES:
    break

4. architecture.md — dispatch table location is stale (once the split is done)

The current docs say:

In utils/jsonl_parser.py, tool results are classified through _parse_tool_result

Once the split is eventually done and _TOOL_RESULT_DISPATCH moves to utils/tool_dispatch.py, this sentence will need updating. (It is currently accurate, since the split hasn't been committed.)

5. Minor: images reassignment style in _process_user

if tr_images:
    images = (images or []) + tr_images

This is harmless but constructs a new list. A cleaner alternative:

if tr_images:
    images = list(images or []) + tr_images
# or simply:
images = (images or []) + tr_images  # already fine, just note it creates a new list

Summary

Area Severity Finding
PR scope 🔴 Critical New modules (tool_dispatch.py, session_peek.py, jsonl_helpers.py) were not committed — the monolith split described in the PR body is absent from the diff
scripts/export.py docstring ✅ Good Accurate and useful exit-code clarification
_strip_system_tags 🟡 Minor import re inside function should be at module level
task_message predicate 🟠 Medium Intentionally broad predicate shadows narrower task predicates; acknowledged but carries classification risk
quick_session_info 🟡 Minor Off-by-one (reads 81 lines, not 80); constant undocumented
architecture.md ℹ️ Info Accurate today; will need updating once the split actually lands

Recommendation: Either push the refactoring commits so the branch matches the PR description, or update the PR title/description to reflect that this is only the exit-code documentation fix (which is a valid standalone change).

@clean6378-max-it clean6378-max-it deleted the feat/cli-export-exit-codes branch May 28, 2026 18:51
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.

claude-code-chat-browser: Extract JSONL parser monolith into focused modules

1 participant