HookStackGitHub
Back to catalogue
NotificationSessionEndSessionEndOn Claude Code session end· non-blocking

End-of-session audit log

At session end, writes an audit line (timestamp, end reason, directory) to a local log file for DevSecOps traceability.

Use cases

  • Traceability
  • DevSecOps audit
  • Usage reporting

Providers & tags

Claude Code
#notification#audit#logging#devsecops

settings.json fragment

{
  "hooks": {
    "SessionEnd": [
      {
        "hooks": [
          {
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/audit-log.mjs",
            "type": "command"
          }
        ]
      }
    ]
  }
}

Script · .claude/hooks/audit-log.mjs

#!/usr/bin/env node
// Enregistre un résumé de session dans ~/.claude/audit-log.jsonl (SessionEnd)
import { readFileSync, appendFileSync, mkdirSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import { fileURLToPath } from 'url';

export function run(
  input,
  {
    append = appendFileSync,
    mkdir = mkdirSync,
    home = homedir(),
    projectDir = process.env.CLAUDE_PROJECT_DIR,
    now = () => new Date().toISOString(),
  } = {},
) {
  const logDir = join(home, '.claude');
  mkdir(logDir, { recursive: true });

  const entry = {
    timestamp: now(),
    project: projectDir?.split('/').pop() ?? 'unknown',
    session_id: input.session_id ?? null,
    total_cost_usd: input.total_cost_usd ?? null,
    num_turns: input.num_turns ?? null,
  };

  append(join(logDir, 'audit-log.jsonl'), JSON.stringify(entry) + '\n');
  return entry;
}

/* v8 ignore next 4 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
  const input = JSON.parse(readFileSync(0, 'utf8'));
  run(input);
}