# PreToolUse hook for Edit|Write: refuses hand edits to files under Data/Migrations/. # Claude Code sends the tool call as JSON on stdin. Exit code 2 blocks the call and # Claude receives the stderr text as the reason. try { $stdin = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), (New-Object System.Text.UTF8Encoding $false)) $raw = $stdin.ReadToEnd() $call = $raw | ConvertFrom-Json -ErrorAction Stop } catch { # Fail closed: a guard that cannot read its input must not let the edit through. [Console]::Error.WriteLine("guard-migrations.ps1 could not read the hook input: $($_.Exception.Message)") exit 2 } $root = if ($env:CLAUDE_PROJECT_DIR) { $env:CLAUDE_PROJECT_DIR } else { $call.cwd } $root = [IO.Path]::GetFullPath($root).TrimEnd('\') # Keep what the hook received and a one-line summary (successful hooks show nothing in the transcript). $logDir = Join-Path $root '.claude\hooks\logs' New-Item -ItemType Directory -Force -Path $logDir | Out-Null Add-Content -Path (Join-Path $logDir 'hook-input.jsonl') -Value $raw.Trim() -Encoding UTF8 function Write-HookLog([string]$text) { $since = [math]::Round(((Get-Date) - (Get-Process -Id $PID).StartTime).TotalSeconds, 2) $codePage = try { [Console]::InputEncoding.CodePage } catch { 'none' } $line = '{0} PreToolUse guard-migrations.ps1 {1} {2} cp={3} {4} process_s={5}' -f (Get-Date).ToString('o'), (Get-Process -Id $PID).Path, $PSVersionTable.PSVersion, $codePage, $text, $since Add-Content -Path (Join-Path $logDir 'hooks.log') -Value $line -Encoding UTF8 } # On Windows the path arrives absolute with backslashes; compare with forward slashes. $path = "$($call.tool_input.file_path)" -replace '\\', '/' $shown = $path.Replace(($root -replace '\\', '/') + '/', '') if ($path -match '/Data/Migrations/') { Write-HookLog "tool=$($call.tool_name) BLOCKED $shown" [Console]::Error.WriteLine('Blocked by .claude/hooks/guard-migrations.ps1: files under Data/Migrations/ are generated by EF Core and are never edited by hand. Change the entity class or AppDbContext instead, then create a new migration from the repository root with: dotnet ef migrations add --project src/AppointmentDesk.Api') exit 2 } Write-HookLog "tool=$($call.tool_name) allowed $shown" exit 0