One-Line Summary: Write a background worker triggered by SessionStart that audits the last five commits — flagging anything that looks regression-prone — so that whenever Claude Code opens a session in the project, the user sees pre-reviewed findings before they ask.

Prerequisites: Step 6 (slash commands wired). The SessionStart hook from Step 4 already triggers the worker; this step writes the worker.


What the Worker Does

When a Claude Code session starts in the project, the session-start.sh hook (Step 4) launches workers/audit-worker.ts in the background. The worker:

  1. Looks at the last five commits.
  2. Spawns a focused sub-agent to audit each commit's changes.
  3. Aggregates findings into a per-project audit report.
  4. Writes the report to .claude/audit-findings.md.

The worker doesn't change anything; it surfaces. The main agent, on the user's next prompt, can read the audit findings and proactively raise them.

Write the Worker

Create workers/audit-worker.ts:

#!/usr/bin/env tsx
/**
 * Background audit worker. Runs on Claude Code SessionStart.
 *
 * Inspects the last 5 commits and writes an audit report to
 * .claude/audit-findings.md.
 *
 * Designed to be cheap: read-only operations, single sub-agent invocation,
 * exits early if nothing has changed since the last audit.
 */
 
import { execFileSync } from "node:child_process";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
 
const projectDir = process.argv[2] ?? process.cwd();
process.chdir(projectDir);
 
const claudeDir = join(projectDir, ".claude");
const reportPath = join(claudeDir, "audit-findings.md");
const stateFile = join(claudeDir, "audit-state.json");
 
mkdirSync(claudeDir, { recursive: true });
 
// Get last 5 commit hashes
const commitOutput = execFileSync(
  "git",
  ["log", "-5", "--format=%H|%s|%ar|%an"],
  { encoding: "utf8", cwd: projectDir }
).trim();
 
const commits = commitOutput.split("\n").map((line) => {
  const [hash, subject, age, author] = line.split("|");
  return { hash, subject, age, author };
});
 
if (commits.length === 0) {
  process.exit(0);
}
 
// Skip if we already audited up to this latest commit
const latestHash = commits[0].hash;
let prevState: { lastAudited?: string } = {};
if (existsSync(stateFile)) {
  prevState = JSON.parse(readFileSync(stateFile, "utf8"));
}
 
if (prevState.lastAudited === latestHash) {
  process.exit(0); // Nothing new to audit
}
 
// For each commit, get the diff stats (cheap heuristic for what to dig into)
const commitDiffs = commits.map(({ hash, subject, age }) => {
  const stat = execFileSync(
    "git",
    ["show", "--stat", "--format=", hash],
    { encoding: "utf8", cwd: projectDir }
  ).trim();
  return { hash, subject, age, stat };
});
 
// Build a short audit report. In a real plugin this would dispatch a
// Claude sub-agent; for the worker (running outside a session) we'll
// produce a heuristic report and let the main agent decide whether to
// dig deeper.
const lines: string[] = [];
lines.push(`# Audit findings (auto-generated ${new Date().toISOString()})`);
lines.push("");
lines.push("Recent commits inspected by the audit worker on session start:");
lines.push("");
 
let suspicious = 0;
 
for (const c of commitDiffs) {
  const filesChanged = c.stat.split("\n").filter((l) => l.includes("|")).length;
  const insertions = (c.stat.match(/(\d+) insertion/) ?? ["", "0"])[1];
  const deletions = (c.stat.match(/(\d+) deletion/) ?? ["", "0"])[1];
 
  // Suspicious heuristic: very large commits, or commits touching auth/security keywords
  const isSuspiciouslyBig =
    Number(insertions) + Number(deletions) > 500 ||
    Number(filesChanged) > 20;
  const touchesSensitive =
    /auth|secret|crypt|token|password|admin|root/i.test(c.subject) ||
    /auth|secret|crypt|token|password|admin|root/i.test(c.stat);
 
  const flag = isSuspiciouslyBig || touchesSensitive ? "⚠" : "•";
  if (flag === "⚠") suspicious++;
 
  lines.push(`${flag} **${c.hash.slice(0, 8)}** _(${c.age})_ — ${c.subject}`);
  lines.push(`   ${filesChanged} files, +${insertions} / -${deletions}`);
  if (touchesSensitive) {
    lines.push(`   _flagged: touches auth/security keywords_`);
  }
  if (isSuspiciouslyBig) {
    lines.push(`   _flagged: large commit (consider /review on this hash)_`);
  }
  lines.push("");
}
 
lines.push("");
if (suspicious > 0) {
  lines.push(
    `**${suspicious} commit(s) flagged.** Run \`/review <commit-hash>\` on flagged commits for a full review.`
  );
} else {
  lines.push("_No suspicious commits in the last 5._");
}
 
writeFileSync(reportPath, lines.join("\n"));
 
// Persist state so we don't re-audit
writeFileSync(stateFile, JSON.stringify({ lastAudited: latestHash, at: new Date().toISOString() }));
 
console.log(`audit-worker: wrote ${reportPath}`);

A few intentional design choices:

  • Read-only: the worker uses git log and git show --stat. It does not edit, run tests, or shell out to anything that could mutate state.
  • Heuristic, not LLM: the worker uses cheap heuristics (commit size, sensitive keywords) to flag commits worth a full review. We don't burn LLM tokens on every session start. The user can run /review on a flagged commit if they want a real review.
  • Stateful: re-audit only happens when there are new commits.
  • Cheap: it runs on session start, blocking nothing.

How the Main Agent Uses the Report

The audit report lands at .claude/audit-findings.md. We want the main agent to read it on session start. There are two ways:

Option A: CLAUDE.md reference. Add a line to CLAUDE.md (in the test project) that says:

At the start of every session, check `.claude/audit-findings.md` for any flagged commits and proactively mention them to the user.

Option B: Explicit SessionStart hook output. Modify session-start.sh to read the report and inject it as additional context.

Option A is cleaner for this blueprint. In production you'd want B for reliability.

Test the Worker

Trigger a fresh Claude Code session in the test project. The SessionStart hook should launch the worker. Wait a few seconds, then:

cat .claude/audit-findings.md

You should see the audit report. If the worker errored, check .claude/audit-worker.log.

Now make a fake "suspicious" commit:

echo "// admin override" >> auth.ts
git add auth.ts
git commit -m "Add admin auth override"

Restart Claude Code. The audit worker should flag this commit for auth keyword.

Commit

cd ~/dev/harness-codereview
git add workers/
git commit -m "Add background audit worker (heuristic, runs on SessionStart)"

What This Step Did

Exercised:

  • background-worker-pattern.md — agent that runs without user prompt.
  • audit-and-optimize-workers.md — specifically the audit shape.
  • event-driven-harness-architectures.mdSessionStart as event source.

The worker is intentionally simple. A more sophisticated version would invoke the Claude Agent SDK to spawn a real audit sub-agent. The pattern is the same; the implementation is heavier.


Next: Step 8 - Add Permission Scopes and Injection Defense →