Files
OwnCord/scripts/claude-hook.mjs
T
J3vbandClaude Fable 5 6432e65c56 chore(claude): session-start + pre-bash hooks, deny .env reads; durationcheck + noImplicitOverride (#1463)
* chore(claude): session-start and pre-bash hooks, deny .env reads

scripts/claude-hook.mjs, wired in .claude/settings.json:
- SessionStart warns when core.hooksPath is not .githooks, so a clone or a
  new machine cannot silently run without the repo git hooks.
- PreToolUse on Bash refuses a top-level cd: the tool's shell is persistent,
  so a cd leaks into every later command and a gate can report green from
  the wrong directory. Subshells, git -C and root-relative paths pass.

permissions.deny gains Read(**/.env): the gitignored env files never enter
the model's context. Server/.env.example stays readable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jwaz4CHGAz85Rpypjvto5a

* chore(lint): durationcheck on the server, noImplicitOverride on the client

Both measured at zero hits on dev, so they cost nothing today and only
block regressions: a Duration multiplied by a Duration-typed value, and an
override left behind when its base method is renamed.

rowserrcheck and sqlclosecheck were measured too and rejected: their six
production hits are all correct code (rows.Err is checked inside
scanEventRows behind the rowsScanner interface; the three Close sites close
on every path by hand).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jwaz4CHGAz85Rpypjvto5a

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 18:50:12 +00:00

50 lines
2.0 KiB
JavaScript

// Claude Code hooks for this repository, wired in .claude/settings.json.
//
// session-start warn when the repo git hooks are not installed, so a clone
// or a new machine cannot silently run without them.
// pre-bash refuse a top-level `cd`. The Bash tool's shell is
// persistent, so a `cd` leaks its directory into every later
// command; a relative path then resolves somewhere else and a
// gate can report a green result from the wrong directory.
// Use a subshell `( cd DIR && ... )`, `git -C DIR`, or paths
// from the repository root instead.
//
// Exit code 2 from a PreToolUse hook blocks the tool call and shows stderr to
// the model; anything else lets it through.
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
const mode = process.argv[2];
if (mode === "session-start") {
let hooksPath = "";
try {
hooksPath = execFileSync("git", ["config", "core.hooksPath"], {
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
}).trim();
} catch {
// unset: git exits 1
}
if (hooksPath !== ".githooks") {
console.log(
"WARNING: the repo git hooks are not installed (core.hooksPath is not .githooks). Run `npm run hooks:install` at the repository root.",
);
}
} else if (mode === "pre-bash") {
let command = "";
try {
command = JSON.parse(readFileSync(0, "utf8")).tool_input?.command ?? "";
} catch {
// no or malformed input: nothing to check
}
// A `cd` that starts the command or follows a chain operator or newline is a
// top-level statement; `( cd DIR && ... )` is not matched.
if (/(^|\n|&&|\|\||;)\s*cd(\s|$)/.test(command)) {
console.error(
"Blocked: a top-level `cd` leaks the persistent shell cwd into every later command. Use a subshell `( cd DIR && ... )`, `git -C DIR`, or paths from the repository root.",
);
process.exit(2);
}
}