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 “finalize all” capture operations to reliably use the session ID from session-start event data when no explicit session ID is provided.
    • Kept correct precedence: --session-id overrides session-start data, and environment-provided values are respected when applicable.
    • When no session ID is available anywhere, the command now safely performs no action and returns empty results.
  • Configuration

    • Ensured session-start events automatically trigger “capture finalize-all.”
  • Tests

    • Added coverage for the stdin payload, override behavior, and no-session no-op scenarios.

@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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a42f6aaf-1576-49ab-ba52-b8a20e921069

📥 Commits

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

📒 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

Walkthrough

capture finalize-all now documents SessionStart stdin session resolution and is covered by adapter wiring and CLI regression tests 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

  • vouchdev/vouch#337: Adds related Claude adapter hook wiring used by the capture finalize-all tests.

Suggested labels: adapters

Suggested reviewers: plind-junior, dripsmvcp, jsdevninja

🚥 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 and concisely summarizes the main change: reading the session id from stdin payload in finalize-all.
Linked Issues check ✅ Passed The changes satisfy #492 by reading session_id from the SessionStart stdin payload, keeping fallbacks, and adding regression coverage.
Out of Scope Changes check ✅ Passed The added tests and docstring update are directly tied to the finalize-all stdin session-id fix and adapter wiring coverage.
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

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

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