-
Notifications
You must be signed in to change notification settings - Fork 0
Description
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.statsdict (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_pathif 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:
- Find the mirror transcript file
- Open it and scroll through
- 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.py—run()method'sfinallyblock
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?"