Skip to content

fix(capture): read session id from stdin payload in finalize-all - #524

Open
Tet-9 wants to merge 1 commit into
vouchdev:testfrom
Tet-9:fix/492-sessionstart-finalize-all-no-session-id
Open

fix(capture): read session id from stdin payload in finalize-all#524
Tet-9 wants to merge 1 commit into
vouchdev:testfrom
Tet-9:fix/492-sessionstart-finalize-all-no-session-id

Conversation

@Tet-9

@Tet-9 Tet-9 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

vouch capture finalize-all is wired into the claude-code SessionStart hook to sweep buffers left by sessions that ended without SessionEnd (#304), but it could never do that: the shipped hook (adapters/claude-code/.claude/settings.json) passes no --session-id, and nothing sets VOUCH_SESSION_ID. sid was always empty, so the command silently no-op'd on every session start — orphaned buffers just accumulated in .vouch/captures/ indefinitely instead of rolling up into pending proposals, meaning those sessions never reached the review queue at all.

Unlike capture finalize, finalize-all never read the stdin hook payload, which is where claude-code actually puts session_id.

Root cause

test_adapter_settings_wires_capture_hooks asserted observe/finalize/banner were wired but never finalize-all, and every existing CLI test for the command passed --session-id or the env var explicitly — the shipped bare-hook configuration was the one path nothing exercised.

Changes

  • cli.py: capture_finalize_all_cmd now reads the session id off the stdin JSON payload (same read pattern capture_finalize_cmd already uses at SessionEnd), keeping --session-id and VOUCH_SESSION_ID as fallbacks in that precedence order.
  • test_capture.py:
    • test_adapter_settings_wires_capture_hooks now also asserts capture finalize-all is wired to SessionStart — closing the exact gap that let this ship unnoticed.
    • New regression tests: session id read from stdin payload alone (the real-world hook scenario), --session-id still overriding the payload when both are given, and the genuinely-session-less case staying a silent no-op.

Testing

  • New tests pass; full test_capture.py: 58 passed.
  • ruff check, mypy (this PR's files): clean.

Checklist

  • Target Branch: test
  • Testing: build passes, test_capture.py green (58 passed)
  • No UI/layout/styling changed
  • UI evidence — N/A, pure backend logic fix, no UI surface touched
  • Security Checklist: both boxes checked

Closes #492

Summary by CodeRabbit

  • Bug Fixes

    • Improved/verified capture finalize-all session ID handling: it uses the session-start event data when no explicit session ID is provided.
    • Ensures correct precedence: --session-id overrides session-start data, and missing session IDs result in a safe no-op with empty outputs.
  • Configuration

    • Updated session-start hook wiring so capture finalize-all is correctly included for automatic triggering.
  • Tests

    • Added regression tests covering stdin/session-start precedence, --session-id override behavior, and the no-session no-op case.

@Tet-9
Tet-9 requested a review from plind-junior as a code owner July 18, 2026 00:27
@github-actions github-actions Bot added cli command line interface tests tests and fixtures size: S 50-199 changed non-doc lines labels Jul 18, 2026
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

capture finalize-all documents SessionStart stdin session resolution and adds hook wiring and regression coverage for payload sourcing, option precedence, and missing-session no-op behavior.

Changes

Capture finalize-all flow

Layer / File(s) Summary
Session ID resolution
src/vouch/cli.py
The command documentation describes reading session_id from SessionStart stdin and falling back to --session-id and VOUCH_SESSION_ID.
Hook wiring and regression validation
tests/test_capture.py
Tests require the SessionStart capture finalize-all hook and cover stdin sourcing, option precedence, and silent no-op behavior without a session ID.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: adapters

Suggested reviewers: plind-junior

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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 change: reading the session ID from the stdin payload in finalize-all.
Linked Issues check ✅ Passed The changes match issue #492 by reading session_id from the SessionStart payload and preserving option/env fallbacks with tests.
Out of Scope Changes check ✅ Passed The docstring update and added tests are directly tied to the requested finalize-all behavior and do not introduce unrelated scope.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

🧹 Nitpick comments (1)
src/vouch/cli.py (1)

2459-2468: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider extracting the sys.stdin JSON parsing logic.

This logic for safely reading and parsing the JSON payload from sys.stdin is now duplicated identically between capture_finalize_cmd (lines 2388-2397) and capture_finalize_all_cmd. Extracting it into a shared helper function would DRY the code and prevent future divergence.

♻️ Proposed refactor to deduplicate stdin payload parsing
def _read_stdin_payload() -> dict[str, Any]:
    if not sys.stdin.isatty():
        raw = sys.stdin.read()
        if raw.strip():
            try:
                loaded = json.loads(raw)
                if isinstance(loaded, dict):
                    return loaded
            except json.JSONDecodeError:
                pass
    return {}

Then simplify the logic in the commands:

-    payload: dict[str, Any] = {}
-    if not sys.stdin.isatty():
-        raw = sys.stdin.read()
-        if raw.strip():
-            try:
-                loaded = json.loads(raw)
-                if isinstance(loaded, dict):
-                    payload = loaded
-            except json.JSONDecodeError:
-                payload = {}
+    payload = _read_stdin_payload()
🤖 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 `@src/vouch/cli.py` around lines 2459 - 2468, Extract the duplicated stdin JSON
parsing from capture_finalize_cmd and capture_finalize_all_cmd into a shared
_read_stdin_payload helper. Preserve the current behavior: read only from
non-TTY stdin, return a parsed dictionary for valid JSON objects, and return an
empty dictionary for empty, invalid, or non-object input; update both commands
to use the helper.
🤖 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.

Nitpick comments:
In `@src/vouch/cli.py`:
- Around line 2459-2468: Extract the duplicated stdin JSON parsing from
capture_finalize_cmd and capture_finalize_all_cmd into a shared
_read_stdin_payload helper. Preserve the current behavior: read only from
non-TTY stdin, return a parsed dictionary for valid JSON objects, and return an
empty dictionary for empty, invalid, or non-object input; update both commands
to use the helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6546fa29-63ea-4c9b-85e7-602fb27b0833

📥 Commits

Reviewing files that changed from the base of the PR and between dec9e2b and 816a03b.

📒 Files selected for processing (2)
  • src/vouch/cli.py
  • tests/test_capture.py

@Tet-9
Tet-9 force-pushed the fix/492-sessionstart-finalize-all-no-session-id branch from 816a03b to bf8d935 Compare July 24, 2026 08:13
@Tet-9

Tet-9 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@plind-junior , if you may now.

@Tet-9

Tet-9 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

🧹 Nitpick comments (1)

src/vouch/cli.py (1)> 2459-2468: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider extracting the sys.stdin JSON parsing logic.
This logic for safely reading and parsing the JSON payload from sys.stdin is now duplicated identically between capture_finalize_cmd (lines 2388-2397) and capture_finalize_all_cmd. Extracting it into a shared helper function would DRY the code and prevent future divergence.

♻️ Proposed refactor to deduplicate stdin payload parsing

def _read_stdin_payload() -> dict[str, Any]:
    if not sys.stdin.isatty():
        raw = sys.stdin.read()
        if raw.strip():
            try:
                loaded = json.loads(raw)
                if isinstance(loaded, dict):
                    return loaded
            except json.JSONDecodeError:
                pass
    return {}

Then simplify the logic in the commands:

-    payload: dict[str, Any] = {}
-    if not sys.stdin.isatty():
-        raw = sys.stdin.read()
-        if raw.strip():
-            try:
-                loaded = json.loads(raw)
-                if isinstance(loaded, dict):
-                    payload = loaded
-            except json.JSONDecodeError:
-                payload = {}
+    payload = _read_stdin_payload()

🤖 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 `@src/vouch/cli.py` around lines 2459 - 2468, Extract the duplicated stdin JSON
parsing from capture_finalize_cmd and capture_finalize_all_cmd into a shared
_read_stdin_payload helper. Preserve the current behavior: read only from
non-TTY stdin, return a parsed dictionary for valid JSON objects, and return an
empty dictionary for empty, invalid, or non-object input; update both commands
to use the helper.

🤖 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.

Nitpick comments:
In `@src/vouch/cli.py`:
- Around line 2459-2468: Extract the duplicated stdin JSON parsing from
capture_finalize_cmd and capture_finalize_all_cmd into a shared
_read_stdin_payload helper. Preserve the current behavior: read only from
non-TTY stdin, return a parsed dictionary for valid JSON objects, and return an
empty dictionary for empty, invalid, or non-object input; update both commands
to use the helper.

ℹ️ Review info

review this

@Tet-9
Tet-9 force-pushed the fix/492-sessionstart-finalize-all-no-session-id branch from bf8d935 to 3f936c6 Compare July 24, 2026 21:21
The SessionStart finalize-all backstop (vouchdev#304) never fires: the shipped
hook passes no --session-id and sets no VOUCH_SESSION_ID, so the
command always saw an empty session id and silently no-op'd, letting
orphaned buffers accumulate in .vouch/captures/ indefinitely instead
of rolling up into pending proposals.

- Read the session id off the stdin hook payload, mirroring how
  capture_finalize_cmd already does it at SessionEnd
- Keep --session-id and VOUCH_SESSION_ID as fallbacks for direct/
  manual invocation
- Assert finalize-all is actually wired to SessionStart in
  test_adapter_settings_wires_capture_hooks (the gap that let this
  ship unnoticed)
- Add regression tests for stdin-payload precedence, option-overrides-
  payload, and the genuinely-session-less no-op case

Closes vouchdev#492
@Tet-9
Tet-9 force-pushed the fix/492-sessionstart-finalize-all-no-session-id branch from 3f936c6 to f756e9e Compare July 27, 2026 12:03

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/vouch/cli.py (1)

5427-5429: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

validate --seeds before calling run_seeds.

Reject empty or whitespace-only values by raising click.BadParameter(...) instead of falling back to a single-seed run. Catch malformed tokens and convert ValueError to a user-facing parameter error so vouch bench run --seeds "1,a" does not expose raw parsing output.

🤖 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 `@src/vouch/cli.py` around lines 5427 - 5429, Validate the --seeds value before
invoking bench_mod.run_seeds: reject empty or whitespace-only input with
click.BadParameter, catch int conversion ValueError for malformed tokens, and
convert it to a user-facing parameter error. Preserve valid comma-separated seed
parsing and pass the resulting seed_list to run_seeds.
🤖 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.

Outside diff comments:
In `@src/vouch/cli.py`:
- Around line 5427-5429: Validate the --seeds value before invoking
bench_mod.run_seeds: reject empty or whitespace-only input with
click.BadParameter, catch int conversion ValueError for malformed tokens, and
convert it to a user-facing parameter error. Preserve valid comma-separated seed
parsing and pass the resulting seed_list to run_seeds.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c68d04a6-32a6-4e1a-9ee2-2a0dd57b566b

📥 Commits

Reviewing files that changed from the base of the PR and between 3f936c6 and f756e9e.

📒 Files selected for processing (2)
  • src/vouch/cli.py
  • tests/test_capture.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_capture.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli command line interface size: S 50-199 changed non-doc lines tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant