HookStack
Back to catalogue
WorkflowConfigChange

Configuration change audit log

Every settings.json change is on the record

Records every change to a Claude Code configuration file (settings.json, skills, etc.) into a timestamped audit log for traceability and compliance.

What does the Configuration change audit log hook do?

Configuration change audit log is a Claude Code ConfigChange hook. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Every settings.json change is on the record.

As a ConfigChange hook it runs after the action, reacting to what just happened rather than blocking it. Because it is a deterministic Node.js script, it executes on every matching event without relying on the model to remember — the guarantee that makes agentic workflows safe to automate.

Use cases

  • Track who changed the Claude Code configuration and when
  • Compliance audit on settings.json changes
  • Detect unauthorized configuration changes

Tags

#audit#config#compliance#logging

settings.json fragment

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

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

#!/usr/bin/env node
// @hookstack config-change-audit-log
// Journalise les changements de configuration Claude Code (ConfigChange)
import { appendFileSync, mkdirSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node: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 = {
		ts: now(),
		project: projectDir?.split("/").pop() ?? "unknown",
		change: input.change ?? input,
	};

	append(join(logDir, "config-changes.jsonl"), `${JSON.stringify(entry)}\n`);
	return { entry, message: "[config-audit] Changement journalise.\n" };
}

/* v8 ignore next 5 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
	const input = JSON.parse(readFileSync(0, "utf8"));
	const result = run(input);
	process.stderr.write(result.message);
}

Learn more

Related hooks