Skip to content

No session summary printed on exit #46

@martymcenroe

Description

@martymcenroe

Summary

When an unleashed session ends (Claude Code exits, user presses Ctrl+C, or the PTY dies), the wrapper just... stops. There's no summary of what happened during the session. The user is dropped back to their shell prompt with no indication of:

  • How long the session ran
  • How many permissions were auto-approved
  • How many sentinel checks occurred (and their verdicts)
  • Whether any commands were blocked
  • How many friction prompts were logged
  • The path to the session's mirror transcript and logs

Current Exit Behavior

[session runs for 45 minutes]
[Claude Code exits]
$                    ← user is back at shell, no context

Proposed Exit Behavior

[session runs for 45 minutes]
[Claude Code exits]

── unleashed session summary ──────────────────
  Duration:        45m 12s
  Approvals:       47
  Sentinel:        12 local-allow, 3 api-allow, 1 blocked
  Mirror:          logs/mirror-2026-02-14_14-30.txt (2.1 KB)
  Friction log:    logs/friction-2026-02-14_14-30.jsonl
───────────────────────────────────────────────
$

Implementation

The data is already available:

  • Duration: time.time() - session_start (session_start already tracked)
  • Approval count: increment a counter in do_approval()
  • Sentinel stats: self.sentinel_gate.stats dict (already has local_allow, local_block, api_allow, api_block, api_error)
  • Mirror path: self.mirror_path (already stored)
  • Friction log: self.friction_logger.log_path if friction logger is active

Print the summary in the finally block of run(), right before cleanup:

finally:
    # Print session summary
    elapsed = time.time() - session_start
    mins, secs = divmod(int(elapsed), 60)
    print(f"\n── unleashed session summary {'─' * 40}", file=sys.stderr)
    print(f"  Duration:     {mins}m {secs}s", file=sys.stderr)
    print(f"  Approvals:    {self.approval_count}", file=sys.stderr)
    if self.sentinel_gate:
        s = self.sentinel_gate.stats
        print(f"  Sentinel:     {s['local_allow']} local-allow, {s['api_allow']} api-allow, "
              f"{s['local_block'] + s['api_block']} blocked, {s['api_error']} errors", file=sys.stderr)
    if self.mirror_path:
        size = os.path.getsize(self.mirror_path) if os.path.exists(self.mirror_path) else 0
        print(f"  Mirror:       {self.mirror_path} ({size // 1024:.1f} KB)", file=sys.stderr)
    print(f"{'─' * 55}", file=sys.stderr)
    # ... existing cleanup ...

Why This Matters

Unleashed sessions are autonomous by design — the user steps away and comes back to results. A session summary is the bridge between "I left it running" and "here's what happened." Without it, the user must:

  1. Find the mirror transcript file
  2. Open it and scroll through
  3. Mentally reconstruct the session

A 5-line summary on exit gives instant context. It's especially valuable for sentinel adoption: seeing "Sentinel: 47 local-allow, 0 blocked" builds confidence that sentinel isn't interfering with productivity.

Affected Files

  • src/unleashed-c-21.pyrun() method's finally block

Context

This is the lowest-effort, highest-UX-value improvement on this list. All the data already exists in memory — it just needs to be printed. The sentinel stats integration (issue #12 Phase 3) already accumulates the numbers. This issue is about surfacing them to the user at the one moment they're most valuable: when the session ends and they're deciding "did that go well?"

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions