Skip to main content
Youngmin Ko
HomeWritingProjectsPlaygroundKnowledge GraphCV
More ⌄
AboutNowCoursesLinksPrivacy
Search ↗
Writing/Projects, hackathons & contests · Side projects

[Side Project] Two hours of agent work in 35 seconds: building sessionreel

2026-09-19·6 min read
Claude CodeOpen SourcePythonSide ProjectDeveloper Tools
← Previous Post Hanwha Ocean 2026 AX·AI Development (DT): An Additional-Offer Retrospective Next Post →[Side Project] 'Jev-compatible' had no spec: building jevcompat

Loading comments...

← Writing

On this page

  • How it works
  • Hard part 1: making sure the video never lies
  • Hard part 2: not leaking a single secret to a single frame
  • Other details
  • Closing
© 2023-2026 Youngmin. All rights reserved.
rhdudals0505@naver.comGitHubLinkedIn
Built with Next.js.Updated 2026.11.07

Hand a bug to an agent and two hours later it's done: tests pass, the commit is pushed. Then you try to show a teammate or your manager how it was fixed, and you're stuck, because all you have is a 30 MB session log.

There are already tools that display sessions: claude-replay, mindwalk, claude-code-log, zoetrope. They're all viewers. The other person has to open a link, scroll and dig. Nobody does.

So I built the opposite. Instead of asking people to come to the session, it makes something you send. sessionreel reads the session log and renders a 30–60 second mp4.

text
1uvx sessionreel
sessionreel demo
  • GitHub: https://github.com/mandu5/sessionreel
  • PyPI: pip install sessionreel / uvx sessionreel
  • Claude Code plugin: /plugin marketplace add mandu5/sessionreel → /reel

How it works

Claude Code writes every session to ~/.claude/projects//.jsonl: prompts, the agent's messages, every tool call and its result, and even the diff hunks the Edit tool records. Everything a video needs is already there.

The pipeline has four stages.

  1. ingest normalises the JSONL into a handful of event kinds (prompts, messages, tool calls). The format is undocumented and changes between versions, so unknown fields are ignored and broken lines are counted and skipped.
  2. redact removes secrets (more below).
  3. story finds the narrative. The spine is red → fix → green: the same check failing, files changing, the same check passing.
  4. render draws each frame with Pillow and pipes raw RGB into ffmpeg for H.264. No browser, no Node. A 35-second reel renders in about 30 seconds on an M1 Pro.

Hard part 1: making sure the video never lies

"Why not give the log to an LLM and ask for a summary video?" That was my first thought too. But this is a video people will post publicly; a single untrue scene ruins it. So the story engine is deterministic and every caption is built from numbers in the log.

Deterministic isn't the same as honest, though. I ran an independent reviewer agent against 29 real sessions, and it found:

  • Red and green from different commands paired up. A failing ruff check A.py and a passing ruff format B.py became "fixed". A full run with 5 failures followed by a narrowed -k empty run with 1 pass was shown as "5 failed → 1 passed". → Pair only the same check with the same selectors (paths, -k, -m), and never accept a green run that narrowed the scope.
  • Reading a non-result as a result. A test sent to the background ("Command running in background") counted as a pass; a tool call the user rejected counted as a failure. → "No failure visible" is not "passed". A run is green only with a positive signal (N passed, All checks passed, …).
  • Fake "Shipped". A Python script in a heredoc that merely contained the string git commit, or running grep "git push", produced a "Shipped" scene; so did git push --dry-run. → Split shell lines into segments, treat heredoc bodies as data, and judge only at the start of a segment. A local commit is captioned "Committed", not "Shipped".
  • Overclaiming "fix". Not every file changed between red and green is the fix; a doc file showed up as "Fix in SUBMISSION.md". → A diff is called "Fix in" only when the failure output names that file; everything else is "Changed".

None of these showed up on synthetic test data. They only appeared on real logs. A green test suite built on synthetic fixtures means little until you run real input through it.

Hard part 2: not leaking a single secret to a single frame

Session logs contain everything: reads of .env, emails in commit messages, tokens printed in terminals. Redaction runs on the whole parsed session before the storyboard exists, so later stages never see the originals.

The review still found holes:

  • Diffs were redacted line by line, so a multi-line private key lost only its header; the body survived. → Redact each hunk as one string, then split it again.
  • I only replaced the home path (/Users/name). The username and hostname survived via Claude Code's project folder names (-Users-name-…), differently-cased paths, and shell prompts like name@hostname ~ %, and one hostname contained another person's name.
  • Quoted JSON keys like {"api_key": "…"}, mysql -p and curl -u user:pass were missing from the patterns.
  • A storyboard edited by a person or an agent after planning was rendered without being checked. → Re-redact every string right before rendering, and warn when a caption contains a number that isn't in that scene's data.

There were mistakes in the other direction too. Treating auth as a secret word wiped the name from author: Name and mangled Co-Authored-By:. Over-redact and the video is unreadable; under-redact and a key ends up on X. I kept tuning until a sweep of all 29 real sessions left 0 secrets on screen and the confirmed over-redactions (long file names, LaTeX's \if@…, @ in agent ids) were carved out. It is still pattern-based, which is why the README says: watch it before you post it.

A fun footnote: the moment the repo went public, GitGuardian emailed "8 secrets detected". They were the fake keys in the redaction tests (abcdefgh…, AWS's documented example key). To test a secret detector you need strings that look like secrets, and to another detector they are secrets. The test strings are now assembled at runtime.

Other details

  • Long, multi-task sessions show one episode: from the ask that led to the fix up to the next ask. Otherwise a bug-fix reel would end on an unrelated task's summary.
  • Active time excludes gaps longer than 15 minutes; a session resumed over several days had been reporting "1535 hours".
  • Korean falls back to Pretendard and emoji to Noto Emoji, per character; Korean prompts render as-is, and --lang ko switches the captions.
  • Vertical video (--format tall) and narration with the OS voice (--voice, macOS say); no cloud TTS.
  • As a Claude Code plugin, the agent that did the work rewrites the captions from its own context, and the renderer warns about any number the scene data doesn't support.

Closing

What stuck with me most wasn't a technique but a stance: when a tool speaks for its user, it should say only as much as the evidence supports. That goes for pass counts, for "fixed", and for "shipped".

There are 146 tests and CI on macOS and Ubuntu × Python 3.10 and 3.13. Codex session logs are next. If a reel shows a scene that looks wrong, please open an issue.

  • GitHub: https://github.com/mandu5/sessionreel