-
Notifications
You must be signed in to change notification settings - Fork 42
chore(claude): public-surface-reminder hook + customer-name, Linear-issue, and bugbot-reply rules #1267
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
John-David Dalton (jdalton)
wants to merge
2
commits into
main
from
chore/public-surface-reminder-hook
+145
−0
Closed
chore(claude): public-surface-reminder hook + customer-name, Linear-issue, and bugbot-reply rules #1267
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # public-surface-reminder | ||
|
|
||
| `PreToolUse` hook that **never blocks**. On every `Bash` command that would | ||
| publish text to a public Git/GitHub surface, writes a short reminder to | ||
| stderr so the model re-reads the command with the two rules freshly in | ||
| mind: | ||
|
|
||
| 1. **No real customer or company names.** Use `Acme Inc`. No exceptions. | ||
| 2. **No internal work-item IDs or tracker URLs.** No `SOC-123` / | ||
| `ENG-456` / `ASK-789` / similar, no `linear.app` / `sentry.io` URLs. | ||
|
|
||
| Attention priming, not enforcement. The model is responsible for actually | ||
| applying the rule — the hook just ensures the rule is in the active | ||
| context at the moment the command is about to fire. | ||
|
|
||
| ## What counts as "public surface" | ||
|
|
||
| - `git commit` (including `--amend`) | ||
| - `git push` | ||
| - `gh pr (create|edit|comment|review)` | ||
| - `gh issue (create|edit|comment)` | ||
| - `gh api -X POST|PATCH|PUT` | ||
| - `gh release (create|edit)` | ||
|
|
||
| Any other `Bash` command passes through silently. | ||
|
|
||
| ## Why no denylist | ||
|
|
||
| Because a denylist is itself a customer leak. A file named | ||
| `customers.txt` that enumerates "these are our customers" is worse than | ||
| the bug it tries to prevent. Recognition and replacement happen at write | ||
| time, done by the model, every time. | ||
|
|
||
| ## Exit code | ||
|
|
||
| Always `0`. The hook prints a reminder and steps aside. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| #!/usr/bin/env node | ||
| // Claude Code PreToolUse hook — public-surface reminder. | ||
| // | ||
| // Never blocks. On every Bash command that would publish text to a public | ||
| // Git/GitHub surface (git commit, git push, gh pr/issue/api/release write), | ||
| // writes a short reminder to stderr so the model re-reads the command with | ||
| // the two rules freshly in mind: | ||
| // | ||
| // 1. No real customer/company names — ever. Use `Acme Inc` instead. | ||
| // 2. No internal work-item IDs or tracker URLs — no `SOC-123`, `ENG-456`, | ||
| // `ASK-789`, `linear.app`, `sentry.io`, etc. | ||
| // | ||
| // Exit code is always 0. This is attention priming, not enforcement. The | ||
| // model is responsible for actually applying the rule — the hook just makes | ||
| // sure the rule is in the active context at the moment the command is about | ||
| // to fire. | ||
| // | ||
| // Deliberately carries no list of customer names. Recognition and | ||
| // replacement happen at write time, not via enumeration. | ||
| // | ||
| // Reads a Claude Code PreToolUse JSON payload from stdin: | ||
| // { "tool_name": "Bash", "tool_input": { "command": "..." } } | ||
|
|
||
| import { readFileSync } from 'node:fs' | ||
|
|
||
| type ToolInput = { | ||
| tool_name?: string | ||
| tool_input?: { | ||
| command?: string | ||
| } | ||
| } | ||
|
|
||
| // Commands that can publish content outside the local machine. | ||
| // Keep broad — better to remind on an extra read than miss a write. | ||
| const PUBLIC_SURFACE_PATTERNS: RegExp[] = [ | ||
| /\bgit\s+commit\b/, | ||
| /\bgit\s+push\b/, | ||
| /\bgh\s+pr\s+(create|edit|comment|review)\b/, | ||
| /\bgh\s+issue\s+(create|edit|comment)\b/, | ||
| /\bgh\s+api\b[^|]*-X\s*(POST|PATCH|PUT)\b/i, | ||
| /\bgh\s+release\s+(create|edit)\b/, | ||
| ] | ||
|
|
||
| function isPublicSurface(command: string): boolean { | ||
| const normalized = command.replace(/\s+/g, ' ') | ||
| return PUBLIC_SURFACE_PATTERNS.some(re => re.test(normalized)) | ||
| } | ||
|
|
||
| function main(): void { | ||
| let raw = '' | ||
| try { | ||
| raw = readFileSync(0, 'utf8') | ||
| } catch { | ||
| return | ||
| } | ||
|
|
||
| let input: ToolInput | ||
| try { | ||
| input = JSON.parse(raw) | ||
| } catch { | ||
| return | ||
| } | ||
|
|
||
| if (input.tool_name !== 'Bash') { | ||
| return | ||
| } | ||
| const command = input.tool_input?.command | ||
| if (!command || typeof command !== 'string') { | ||
| return | ||
| } | ||
| if (!isPublicSurface(command)) { | ||
| return | ||
| } | ||
|
|
||
| const lines = [ | ||
| '[public-surface-reminder] This command writes to a public Git/GitHub surface.', | ||
| ' • Re-read the commit message / PR body / comment BEFORE it sends.', | ||
| ' • No real customer or company names — use `Acme Inc`. No exceptions.', | ||
| ' • No internal work-item IDs or tracker URLs (linear.app, sentry.io, SOC-/ENG-/ASK-/etc.).', | ||
| ' • If you spot one, cancel and rewrite the text first.', | ||
| ] | ||
| process.stderr.write(lines.join('\n') + '\n') | ||
| } | ||
|
|
||
| main() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "name": "@socketsecurity/hook-public-surface-reminder", | ||
| "private": true, | ||
| "type": "module", | ||
| "main": "./index.mts", | ||
| "exports": { | ||
| ".": "./index.mts" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "24.9.2" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
John-David Dalton (@jdalton) I think
stderris only read into Claude's context on exit code 2, which would block the tool call.Looking through the hook docs it doesn't mention reading
stderron exit code 0.I had claude run a quick test, the hook does fire (stream shows
hook_started+hook_responsewith the reminder onstderr,exit_code0), but the subsequenttool_resultsent to the model contains only the Bashstdout.