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:
- Looks at the last five commits.
- Spawns a focused sub-agent to audit each commit's changes.
- Aggregates findings into a per-project audit report.
- 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 logandgit 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
/reviewon 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.mdYou 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.md—SessionStartas 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 →