Skip to content

Commit 522d7c9

Browse files
fix(hooks): scope pre-commit build check to target repo, fix stale symlinks (#2175)
* fix(hooks): scope pre-commit build check to the actual target repo The PreToolUse hook matched any Bash command containing "git commit" and always ran this repo's bun build/lint/typecheck from its own process cwd, even when the command targeted a different repo (e.g. a sibling worktree reached via a leading `cd`). Resolve the real target directory from the command text first, and skip silently for any repo that isn't this one. * fix(lefthook): force-add already-tracked files under gitignored paths The format hook's auto-restage (`git add {staged_files}`) exits non-zero for any staged file that lives under a gitignored directory (e.g. .claude/settings.json, tracked despite .claude/ being ignored for worktree noise), silently aborting the whole commit even though the file was already correctly staged. * fix(producer): repoint stale puppeteer symlinks to the pinned 25.x install packages/producer's tracked node_modules symlinks still pointed at puppeteer@24.43.1, which no longer exists after a fresh install resolves package.json's ^25.2.1 range to 25.3.0 — breaking the producer TypeScript build with a missing puppeteer-core module error. * fix(producer): stop tracking node_modules symlinks packages/producer/node_modules was accidentally swept into a prior commit despite the repo-wide node_modules/ gitignore rule, and its ~30 tracked symlinks silently drift from whatever bun install actually resolves — the exact cause of the stale puppeteer symlinks fixed earlier in this branch. CI always runs bun install --frozen-lockfile before building, so nothing depends on these being pre-committed.
1 parent 05c3b55 commit 522d7c9

35 files changed

Lines changed: 2 additions & 35 deletions

.claude/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"type": "command",
99
"timeout": 180,
1010
"statusMessage": "Running build + lint + typecheck before commit…",
11-
"command": "node -e \"\nconst chunks = [];\nprocess.stdin.on('data', d => chunks.push(d));\nprocess.stdin.on('end', () => {\n const input = JSON.parse(Buffer.concat(chunks).toString());\n const cmd = input.tool_input?.command || '';\n if (!/git\\\\s+commit\\\\b/.test(cmd)) process.exit(0);\n const { execSync } = require('child_process');\n const cwd = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();\n const steps = [\n ['bun run build', 'Build'],\n ['bun run lint', 'Lint'],\n ['bun run --filter \\'*\\' typecheck 2>&1 | grep -v \\'vitest\\\\|test\\\\.ts\\' || true', 'Typecheck'],\n ];\n const failures = [];\n for (const [script, label] of steps) {\n try { execSync(script, { cwd, stdio: 'pipe' }); }\n catch (e) {\n failures.push(label + ':\\\\n' + (e.stdout?.toString() || e.message).slice(0, 400));\n }\n }\n if (failures.length > 0) {\n process.stdout.write(JSON.stringify({\n continue: false,\n stopReason: '\\u274c Pre-commit checks failed:\\\\n\\\\n' + failures.join('\\\\n\\\\n') + '\\\\n\\\\nFix the issues above before committing.',\n }));\n }\n});\""
11+
"command": "node -e \"\nconst chunks = [];\nprocess.stdin.on('data', d => chunks.push(d));\nprocess.stdin.on('end', () => {\n const input = JSON.parse(Buffer.concat(chunks).toString());\n const cmd = input.tool_input?.command || '';\n if (!/git\\s+commit\\b/.test(cmd)) process.exit(0);\n const { execSync } = require('child_process');\n const fs = require('fs');\n const path = require('path');\n const os = require('os');\n // A leading 'cd <path>' in the command is the real target directory:\n // Claude Code prefixes this whenever the session's tracked cwd differs\n // from this hook process's own cwd (e.g. a sibling repo worktree) — the\n // hook process itself always inherits the primary working directory,\n // never the command's actual target.\n function resolveTargetDir() {\n const m = cmd.match(/^\\s*cd\\s+('([^']+)'|(\\S+))\\s*(?:&&|;|\\n|$)/);\n if (m) {\n let dir = m[2] || m[3];\n if (dir.startsWith('~')) dir = path.join(os.homedir(), dir.slice(1));\n dir = path.resolve(process.cwd(), dir);\n if (fs.existsSync(dir)) return dir;\n }\n return process.cwd();\n }\n const targetDir = resolveTargetDir();\n let repoRoot;\n try {\n repoRoot = execSync('git rev-parse --show-toplevel', { cwd: targetDir, encoding: 'utf8' }).trim();\n } catch {\n process.exit(0);\n }\n // Only this repo's own bun build/lint/typecheck applies — skip silently\n // for any other repo (e.g. a sibling Go/Python repo touched in the same session).\n const pkgPath = path.join(repoRoot, 'package.json');\n if (!fs.existsSync(pkgPath)) process.exit(0);\n let pkg;\n try {\n pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\n } catch {\n process.exit(0);\n }\n if (pkg.name !== 'hyperframes-monorepo') process.exit(0);\n const steps = [\n ['bun run build', 'Build'],\n ['bun run lint', 'Lint'],\n ['bun run --filter \\'*\\' typecheck 2>&1 | grep -v \\'vitest\\\\|test\\\\.ts\\' || true', 'Typecheck'],\n ];\n const failures = [];\n for (const [script, label] of steps) {\n try {\n execSync(script, { cwd: repoRoot, stdio: 'pipe' });\n } catch (e) {\n failures.push(label + ':\\n' + (e.stdout?.toString() || e.message).slice(0, 400));\n }\n }\n if (failures.length > 0) {\n process.stdout.write(JSON.stringify({\n continue: false,\n stopReason: '❌ Pre-commit checks failed:\\n\\n' + failures.join('\\n\\n') + '\\n\\nFix the issues above before committing.',\n }));\n }\n});\""
1212
}
1313
]
1414
}

lefthook.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pre-commit:
99
# Auto-format and re-stage so the committed snapshot is always formatted.
1010
# Replaces --check which only reports — that left unformatted files in
1111
# commits when the hook ran after the amend snapshot was taken.
12-
run: bunx oxfmt --no-error-on-unmatched-pattern {staged_files} && git add {staged_files}
12+
run: bunx oxfmt --no-error-on-unmatched-pattern {staged_files} && git add -f {staged_files}
1313
skills-manifest:
1414
glob: "skills/**"
1515
# Regenerate the freshness fingerprint when a skill changes, then re-stage

packages/producer/node_modules/.bin/puppeteer

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/producer/node_modules/.bin/tsc

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/producer/node_modules/.bin/tsserver

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/producer/node_modules/.bin/tsx

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/producer/node_modules/@fontsource/archivo-black

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/producer/node_modules/@fontsource/eb-garamond

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/producer/node_modules/@fontsource/ibm-plex-mono

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/producer/node_modules/@fontsource/inter

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)