Blog

I asked Claude to read 30 of my Claude Code sessions and build the mods I needed

The exact prompt, the five mods Claude built from my own sessions, and what broke when they ran for real.

I asked Claude to read 30 of my Claude Code sessions and build the mods I needed

This is the prompt I gave Claude Code:

look at the last 30 sessions and see how we use claude to work - then look at https://code.claude.com/docs/en/plugins/mods/overview and suggest 10 possible mods for our workflow that would help me be better informed as to what each session is doing.

I run four to six Claude Code sessions at a time, most of them in the same repo, and I usually can't tell you what any one of them is doing without clicking into it and scrolling. Mods are new in Claude Code, and I wanted the ideas to come from how I actually work.

Claude pulled the 30 sessions, read through a handful of the transcripts, and came back with ten ideas. Then it built all ten, grouped into five mods, in one evening. I picked the order. Claude wrote, validated and installed them.

The five mods

  • session-fleet: a board of every live session, a needs-you beacon, and a worktree claim guard
  • ship-tracker: every PR from CI to merged to production to live, plus a receipt under every answer
  • session-hygiene: tripwires for my own rules, and a handoff card of loose ends from earlier sessions
  • session-activity: a ledger of everything that left the machine, plus what each session is waiting on
  • context-gauge: how full the context window is, and what was in flight when it compacted

Run the prompt on your own sessions and let your agent build what you need. If you'd rather not, all five of mine are open source at github.com/bennewton999/claude-code-mods, with install steps for each one.

The first draft of this post said I hadn't seen any of them run. So I had Claude set up a throwaway repo with a couple of worktrees and drive five real Claude Code sessions in tmux against it, just to get screenshots. Every screenshot below comes from those sessions, in the terminal. The videos are mocked up and each one says so in the corner. That run caught four bugs, which are near the end. I still haven't used these in my day-to-day work, so a follow-up after a week of real use is coming.

What 30 sessions looked like

The pattern was pretty consistent. Most sessions are in my blog/BlackOps repo, each one goes worktree, PR, CI, merge, Vercel production build, a deploy email, then a note in my brain (collection of markdown files). A few things came up that I didn't love:

  • Five different sessions had worked in the same worktree. One of them hit a git ref lock because another session was fetching at the same moment.
  • Some sessions are enormous. One feature build ran to 1,701 messages, another to 730. Those get compacted, and stuff gets lost when they do.
  • A lot of the time is waiting. Background commands, CI, production builds, subagents.
  • Sessions do outward things mid-flight. They post tweets, apply migrations, send emails, patch notes.
  • Sessions end with loose ends. One left a PR open waiting for me to merge it and I didn't notice until much later.
  • I have a pile of rules in my memory notes (typecheck before a PR, mutation-check new tests, reload the PostgREST schema after a migration). All of them depend on Claude remembering them at the right moment.

What a mod is

A mod is a Claude Code plugin made of JavaScript or TypeScript functions that run inside Claude Code. You register hooks on events (a tool call, a submitted prompt, a turn ending, a part of the UI being drawn) and each hook can watch the event, change it, or answer it itself. Mods can draw a pane beside the transcript, a band above the prompt, a toast, a status line. They work in the terminal and in the Code tab of the desktop app, which is where I live.

All five of these depend on $.store, a small key-value store every session on the machine shares. One session writes, another reads. That's how a session can know what the others are doing.

Claude wrote them with the plugin-authoring skill that ships with Claude Code.

1. session-fleet: a board of every live session

/fleet opens a pane listing every live session: a status dot (waiting, working, idle), the session's first prompt, which worktrees it's touching, branch, PR number, the last thing it did and how long ago. Waiting sessions sort to the top. A worktree that two live sessions are both in gets a warning mark.

The session-fleet pane on the right of a Claude Code terminal: 2 live sessions, 1 waiting on a permission prompt for an Edit in the docs worktree, the other idle in the pricing worktree

Real screenshot from the demo. The receipt line in the middle still carries all five mod names, which was one of the bugs.

Each session writes one record to the shared store under its own key and refreshes a heartbeat every 30 seconds. If a session goes three minutes without a heartbeat, the board treats it as gone. There's no session title API, so the first prompt stands in for the title.

Update, October 4: two additions since this went up. In the desktop app, clicking another session's title in the pane now switches to it, the same as clicking it in the sidebar. Terminal sessions stay plain text. Each session also gets a pill naming its project. Out of the box that's the repo's folder name in gray. To name and color your own, set projectPills in ~/.claude/settings.json. Mine looks like this:

"pluginConfigs": { "session-fleet": { "options": { "projectPills": "BlackOps:blackops|2024-blog:#7c3aed; Spotter:spotter:#059669" } } }

Each entry is Label:regex:color, separated by semicolons. The regex is matched against the repo's folder name, so every worktree of my blog repo shows up as BlackOps.

The same mod does two more things.

A needs-you beacon. When any session is blocked on me, every other session shows a row above the prompt saying which one and why, plus a toast when it starts waiting. Blocked means one of three things: a permission prompt fired, Claude is asking me a question with the question dialog, or its last turn ended with a question mark. The last one is a guess and I already know it will be wrong sometimes.

A Needs you row above the prompt naming the other session, the reason permission: Edit, and how long it has waited

Real screenshot. The other session was sitting on a permission prompt for 25 seconds.

A worktree claim guard. The first session to edit a file or run a mutating git command in a worktree claims it. If a second live session tries, the tool call is held and I get asked, naming the other session, its branch and its last action. Stop refuses the call and tells Claude to ask me or use a different worktree. Claims let go after 30 minutes of no activity. It works out the worktree with git rev-parse, so it still catches it when a session started in my projects folder edits into a worktree somewhere below it.

The worktree guard question in a second session: another session on feature/pricing claimed my-app:pricing 2 minutes ago, write there anyway? with Proceed and Stop options

After a Stop in the demo, Claude came back and asked whether to edit anyway, make a separate worktree, or wait for the other session:

Claude asking whether to edit here anyway, use a separate worktree, or wait, after the guard refused its edit
const rivals = (await all($)).filter( r => r.id !== me.id && isAlive(r, now) && r.claims[tree] && now - r.claims[tree].lastTouch < CLAIM_MS && (!mine || r.claims[tree].since < mine.since), )

Whoever claimed first wins. The later session is the one that gets asked.

2. ship-tracker: where is my PR right now

This one draws a row above the prompt for every PR the session touched. It finds PRs by reading the output of gh pr commands, or I can add one with /ship-track 1107. Every 45 seconds it asks GitHub for the PR state and its checks, and after the merge it looks up the GitHub deployment for the merge commit in the Production environment. Live means that deployment reports success.

The demo repo has no GitHub remote, so for the screenshot Claude tracked two public PRs from vercel/ai-chatbot. One merged and live, one with a failing check:

Two ship band rows above the prompt: #1529 with CI, merged, prod build and live all checked, and #1550 with a failed CI check

If a PR has been merged for 15 minutes and there's still no production deployment, the row says no prod deploy! and it toasts. That check is there because my Vercel git trigger has died silently before, and the only way I found out was noticing a feature wasn't live.

It also writes a receipt line after every answer: how long the turn took, how many tool calls, which files were edited, how many shell commands, anything outward (a push, a PR, an MCP write), and how full the context is. Turns with no tool calls and under 20 seconds get nothing. You can see one with out: git push in the push screenshot further down.

One thing was left out on purpose. I have a Rocky voice line (the Eridian engineer from Project Hail Mary) that plays when a deploy is verified live. That's already wired into my Claude instructions, so having the mod cheer too would double it.

3. session-hygiene: my rules, checked automatically

My memory notes are full of rules I wrote after something broke. This mod turns some of them into tripwires. When one trips I get a dim line in the transcript and a toast, and Claude gets a reminder attached to the tool result, so it can deal with it without me typing anything.

The rules it checks:

  • Pushing or opening a PR after editing TypeScript in my blog repo without running npm run typecheck since. Neither the build nor PR CI typechecks that repo. A missing import once shipped a ReferenceError past a green build and 3,529 passing tests.
  • Pushing with edits made after the last local /code-review. Every extra CI review round costs paid Actions minutes.
  • Writing a new test file. Reminder to mutation-check it (delete the code it tests and confirm it fails).
  • Creating a new top-level page under src/app. It 404s on blackopscenter.com unless the route is added to both host branches of the middleware passthrough list. This has bitten me three times.
  • Applying a migration. Reminder to run NOTIFY pgrst, 'reload schema', and a second warning if the migration creates a table without revoking the default grants from authenticated.
  • Merging after touching package.json or next.config without a local npm run build. PR checks never build the app, so the first real build is production.
  • Writing the Audiowide font into anything. I've banned it everywhere and it keeps showing up.

In the demo, Claude read the reminders and said what it did with them. On the new test file it skipped the mutation check and told me why:

The tripwire line New test plans.test.ts: mutation-check it, followed by Claude explaining it skipped the check because that would mean running the test

After the push it flagged that it hadn't run a review first and offered to run one:

The tripwire Pushed without a local /code-review since the last edit, Claude's summary noting it skipped the review, and a receipt line with out: git push

The typecheck tripwire stayed quiet in the demo, which is right. It only applies to my blog repo.

Two rules hold the command and ask me instead: sourcing an env file into the shell, and anything using the Supabase service-role key. I'd rather get a question than find out afterwards.

A Tripwire question: this command sources an env file, loading every secret in it into the shell. Run it anyway?

The other half of this mod is a handoff card. After each turn a session saves its open loops: PRs it touched that are still open, worktrees with uncommitted or unpushed work, a question it left me. When a new session starts, it lists the loops from sessions that have ended or gone quiet for 30 minutes, after checking each one again live. A PR that got merged in the meantime drops off. /handoff shows the list again.

A new session opening with Open loops from earlier sessions: the CHANGELOG session left 1 uncommitted file in my-app:docs

Real screenshot. The CHANGELOG session had exited without committing its fix.

4. session-activity: everything that left the machine

/ledger opens a pane with every action that went outside the machine: MCP write tools, git push, gh pr create and merge, Vercel deploys, curl writes, artifact publishes, SQL writes. Each row has the time, success or failure, a detail, and the first link in the result. A button flips it to every session in the last 24 hours. Reads don't count, so gh pr view and a plain health check curl stay out of it.

The side-effects ledger pane showing one action this session: a git push at 11:28 with the command that ran

Real screenshot. The demo only pushed to a local folder, so the ledger is short.

A lot of my outward actions go through a BlackOps use_tool wrapper, so the ledger digs out the inner tool name. A tweet shows up as post_tweets with the tweet text as the detail.

Any execute_sql call that writes to the database now stops and asks me first, with the SQL in the question. It strips comments and quoted strings before deciding, so a SELECT that happens to contain the word "update" in a string goes through. Claude tested the classifier against 14 sample calls and it got all of them right. The demo repo had no Supabase connection, so that part only appears in the video.

The other half is a waiting-on tracker. While Claude works, the spinner says what's pending, like Thinking · 2 bg · agent 3m…. When Claude has stopped but background work is still running, a row above the prompt lists it with elapsed time. Background commands come off the list when their completion notification arrives. Foreground calls that run longer than 15 seconds count too.

A Waiting on row above the prompt: bg Wait for demo build, 20 seconds

5. context-gauge: how full is the window

My status line now shows something like ctx 5% · 54k/1000k, plus how many times the session has compacted. I get a toast at 70% and a stronger one at 85%. From 80% there's a row above the prompt with a Compact now button. On a 1M-token window that's a long way off, so the 80% row is still only in the video.

When a compaction happens, the mod grabs what's in flight first: the original task, the latest request, files edited, PR links, the last eight actions. It passes that to the summarizer as instructions to keep as its own section. Afterwards it logs the snapshot and hands it back to Claude as context with my next prompt, so the session knows what it was in the middle of even if the summary dropped something.

const keep = `Preserve exactly, as its own section: the in-flight work below, plus any open PR numbers, branch and worktree names, pending questions to the user, and decisions not yet acted on.\n${snap}` const result = await next({ ...e, instructions: e.instructions ? `${e.instructions}\n\n${keep}` : keep })

In the demo, Claude ran /compact on one of the sessions and then /context-log:

The context-gauge log line Compaction #1 (manual at 5%) and the /context-log output: 54k down to 9k tokens, with the task and last actions

What the demo run caught

Four bugs showed up the first time the mods ran in real sessions. None of them showed up in validation or headless tests.

  • The receipt line came out labeled session-fleet+ship-tracker+session-hygiene+session-activity+context-gauge:, because every mod with a turn hook passed the result along and Claude Code credited all of them. It's its own transcript line now, written by ship-tracker alone.
  • Claude edited the README with a Python one-liner in the shell instead of the Edit tool, so that session never claimed its worktree. Any shell command run inside a worktree now counts as working there.
  • The handoff card printed a junk character where its line breaks should have been. Transcript lines are single lines, so it writes one per entry now.
  • In the longest demo session, /ledger said it opened and never drew. In a fresh session it drew fine. The built-in diff pane looked like it was sitting in front of it, and that one isn't fixed yet.

A few broke earlier, while building. Two mods both wanted the band above the prompt, and the first version of session-fleet drew its band without passing the event on, which would have hidden ship-tracker's row any time someone needed me. Every band hook now draws its own row and then whatever the next hook returns below it:

const below = await next(e) // ... return ( <Box flexDirection="column"> {myRows} {below} </Box> )

/waiting came back with "no command.run hook answered it" in the session Claude built it in. The code was fine. Claude had been hot-reloading from a temporary folder, and when it moved the mod to its permanent home and deleted the temp copy, the hooks unloaded but the command name stayed registered. Running the same command in a fresh session answered normally. New mods now get written straight into their final folder.

Resuming a session starts a new process, so module variables are empty again. context-gauge first logged a compaction with the task as "(unknown)" for exactly that reason. It now reads the first prompt back out of the transcript at startup.

Try the prompt on your own sessions

The prompt at the top works as is. Change the session count and the question to fit what you can't see. A few things from this round that will save you time:

  • Mods are documented at code.claude.com/docs/en/plugins/mods/overview. Ask for one in a session and Claude writes it with the plugin-authoring skill.
  • Have Claude write each mod straight into its permanent folder and load it in every session with CLAUDE_CODE_PLUGIN_DIRS under env in ~/.claude/settings.json. Moving a hot-reloaded mod is what left my dead /waiting command behind.
  • If you live in the desktop app like I do, set CLAUDE_CODE_PLUGIN_DIR_WATCH to 1 in the same env block. Without it, desktop sessions only pick up a change to a mod after a restart. With it, they reload the mod when its files change. A session reads it at startup, so restart once after adding it.
  • Test slash commands headless with claude -p "/yourcommand" --plugin-dir <folder> before you trust them.
  • Run them in real sessions before you call them done. Validation and headless tests missed all four bugs above.

My env block now looks like this (folders shortened):

"env": { "CLAUDE_CODE_PLUGIN_DIRS": "~/.claude/mods/session-fleet:~/.claude/mods/ship-tracker:~/.claude/mods/session-hygiene:~/.claude/mods/session-activity:~/.claude/mods/context-gauge", "CLAUDE_CODE_PLUGIN_DIR_WATCH": "1" }

If you'd rather not have your agent build its own, install mine. Run /plugin marketplace add bennewton999/claude-code-mods in a session, then /plugin install session-fleet@bennewton-mods and the same for the other four. The repo README covers the other ways to install them.

If you try the prompt, send me what it suggests for your setup. I'd like to see how different the ten ideas come out.

What I actually know

Every mod passes claude plugin validate and a strict TypeScript check. The demo sessions showed the fleet pane, the needs-you row, the worktree guard, the ship band, two tripwires, the env-file hold, the handoff card, the ledger, the waiting-on row and a real compaction snapshot. Claude read the tripwire reminders and acted on them.

What I haven't seen: any of it in the desktop app, the 80% context row, the SQL hold outside a mock, and the receipt line, guard and beacon across a real week of work. I don't know yet whether the needs-you guess is right often enough to trust or whether the worktree guard asks too often. All five are loaded in every new session as of today, so I'll find out.

💌 Want more insights like this?

Subscribe to my newsletter for weekly deep dives into frontend development, AI, and productivity

Join the conversation

Have thoughts about this post? Reply on 𝕏 — I read every one.

Discuss on 𝕏
Loading tweet...

I wrote this post inside BlackOps, my content operating system for thinking, drafting, and refining ideas — with AI assistance.

If you want the behind-the-scenes updates and weekly insights, subscribe to the newsletter.

Related Posts