HookStackGitHub
Back to catalogue
WorkflowPermissionDenied

Log auto-mode permission denials

Appends every auto-mode permission denial to a log file with timestamp, tool name, input and denial reason. Provides visibility into what the auto-mode classifier is blocking, helping you tune allow/deny rules over time.

Use cases

  • Understand which tool calls auto-mode is silently blocking in autonomous sessions
  • Tune allow/deny rules by reviewing the denial log after a session
  • Audit autonomous agent actions for compliance in CI/CD environments

Providers & tags

Claude Code
#auto-mode#permissions#logging#audit#security

settings.json fragment

{
  "hooks": {
    "PermissionDenied": [
      {
        "hooks": [
          {
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/permission-denied-auto-mode-log.mjs",
            "type": "command"
          }
        ]
      }
    ]
  }
}

Script · .claude/hooks/permission-denied-auto-mode-log.mjs

#!/usr/bin/env node
// Journalise les permissions refusées (PermissionDenied)
import { readFileSync, appendFileSync, mkdirSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

export function run(
  input,
  {
    append = appendFileSync,
    mkdir = mkdirSync,
    projectDir = process.env.CLAUDE_PROJECT_DIR ?? '.',
    now = () => new Date().toISOString(),
  } = {},
) {
  const logPath = join(projectDir, '.claude', 'permission-denied.log');
  try { mkdir(dirname(logPath), { recursive: true }); } catch { /* exists */ }

  const line = `${now()} | ${input.tool_name} | ${JSON.stringify(input.tool_input)} | ${input.reason}\n`;
  append(logPath, line);
  return line;
}

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