chore(claude): public-surface-reminder hook + customer-name, Linear-issue, and bugbot-reply rules#1267
Closed
John-David Dalton (jdalton) wants to merge 2 commits intomainfrom
Closed
Conversation
Adds a `PreToolUse` hook that 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 two rules freshly in mind:
1. No real customer or company names — use `Acme Corporation`
(or `Acme Corp` shorthand) instead. No exceptions.
2. No internal work-item IDs or tracker URLs — no `SOC-123`,
`ENG-456`, `ASK-789`, `linear.app`, `sentry.io`, etc.
Attention priming, not enforcement. Exit code is always 0. The model
applies 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. A denylist file is
itself a leak. Recognition and replacement happen at write time, every
time.
Mirrors the matching change in socket-repo-template so this rule and
hook propagate fleet-wide. Also adds the written rules to CLAUDE.md so
they exist even when the hook is not installed.
Two small updates mirroring socket-repo-template:
* Replace the `Acme Corporation` / `Acme Corp` two-form placeholder with
a single `Acme Inc` form — easier to remember, less to get wrong at
write time. Updated in the hook's inline comment, stderr message, and
the README.
* Add a rule describing how to reply to Cursor Bugbot: reply on the
inline review-comment thread (`gh api .../pulls/{pr}/comments/{id}/replies`)
so the reply threads under the bot's finding, not as a detached
`gh pr comment` on the PR.
Comment on lines
+13
to
+16
| // 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. |
There was a problem hiding this comment.
John-David Dalton (@jdalton) I think stderr is 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 stderr on exit code 0.
I had claude run a quick test, the hook does fire (stream shows hook_started + hook_response with the reminder on stderr, exit_code 0), but the subsequent tool_result sent to the model contains only the Bash stdout.
Contributor
Author
|
Superseded by #1272 — identical tree (same 5 files, +145/-0), but squashed to a single commit with a tighter body and all checks green. Closing this one to avoid duplicate review. |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What this does
Adds a Claude Code
PreToolUsehook that never blocks. On every Bash command that would publish text to a public Git/GitHub surface, it writes a short reminder to stderr. The model reads that stderr as part of the tool result, so the rules are freshly in context at the exact moment the command is about to run.Think of it as attention priming, not enforcement. The model still does the work of checking — the hook just makes sure the rules aren't forgotten.
Also adds three written rules to
CLAUDE.mdthat mirror the source of truth insocket-repo-template.The rules
1. No real customer or company names
Anywhere public: commits, PR bodies, PR review comments, issue bodies, issue comments, release notes. Replace with
Acme Inc, or drop the reference entirely.No enumerated denylist anywhere — a list of customer names IS the leak we're trying to prevent. Recognition happens at write time, every time.
2. No internal work-item IDs or tracker URLs
No
SOC-123/ENG-456/ASK-789shapes. Nolinear.app/sentry.ioURLs. Tracker coordination stays in the tracker.3. Reply to Cursor Bugbot inline, not detached
When replying to a Cursor Bugbot finding, reply on the inline review-comment thread so the reply threads under the bot's finding. Use:
Find
{comment_id}via:A detached
gh pr commentis wrong for bot findings — it floats on the PR without context.Which commands trigger the reminder hook
Read-only commands pass through silently. The reminder only fires on commands that actually publish content:
git commit(including--amend)git pushgh pr (create|edit|comment|review)gh issue (create|edit|comment)gh api -X POST|PATCH|PUTgh release (create|edit)Anything else (a
git status, anls, agh pr view, agh api -X GET) is ignored and passes through with zero output.Why no denylist
A file that enumerates "these are our customers" is itself a leak — exactly the kind of thing we're trying to prevent from landing in any public surface. So the hook carries no list of company names, no regex, no data. Recognition of "is this a real company?" happens at write time, every time, by the model reading the text it's about to send.
Where it lives
.claude/hooks/public-surface-reminder/index.mts— the hook (Node ESM, reads the tool-input JSON from stdin, always exits 0).claude/hooks/public-surface-reminder/README.md— what/why/how.claude/hooks/public-surface-reminder/package.json— declares@types/node(used).claude/settings.json— chains the hook underPreToolUse→matcher: BashCLAUDE.md changes
Three new lines in
## ABSOLUTE RULES/## SHARED STANDARDS:socket-repo-template/template/CLAUDE.md)Acme Incgh apiinvocationAll three rules exist even when the hook is not installed. The hook is the nudge, the CLAUDE.md is the binding rule.
Commits
7d414c937—chore(claude): add public-surface-reminder hook + customer-name rulea9767af6c—chore(claude): simplify placeholder to Acme Inc, add bugbot-reply ruleSource of truth
The same hook + rules landed on
socket-repo-templatemain:876f42f—feat(template): add public-surface-reminder hook + customer-name rule23289aa—chore(template): simplify placeholder to Acme Inc, add bugbot-reply ruleso they propagate to new repos scaffolded from the template. This PR brings socket-cli in line with template.
Test plan
(echo '{"tool_name":"Bash","tool_input":{"command":"git commit -m test"}}' | node .claude/hooks/public-surface-reminder/index.mts; echo EXIT=$?)— prints reminder, exits 0ls -laas the command — silent, exits 0gh api repos/X/Y/issues/1 -X GET— silent (read-only), exits 0gh api repos/X/Y/issues -X POST ...— prints reminder, exits 0gh pr create --title ... --body ...— prints reminder, exits 0