Skip to content

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
chore/public-surface-reminder-hook
Closed

chore(claude): public-surface-reminder hook + customer-name, Linear-issue, and bugbot-reply rules#1267
John-David Dalton (jdalton) wants to merge 2 commits intomainfrom
chore/public-surface-reminder-hook

Conversation

@jdalton
Copy link
Copy Markdown
Contributor

@jdalton John-David Dalton (jdalton) commented Apr 23, 2026

What this does

Adds a Claude Code PreToolUse hook 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.md that mirror the source of truth in socket-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-789 shapes. No linear.app / sentry.io URLs. 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:

gh api repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies \
  -X POST -f body=...

Find {comment_id} via:

gh api repos/{owner}/{repo}/pulls/{pr}/comments \
  -q '.[] | select(.user.login == "cursor[bot]") | {id, path, line, body: (.body|.[0:200])}'

A detached gh pr comment is 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 push
  • gh pr (create|edit|comment|review)
  • gh issue (create|edit|comment)
  • gh api -X POST|PATCH|PUT
  • gh release (create|edit)

Anything else (a git status, an ls, a gh pr view, a gh 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 under PreToolUsematcher: Bash

CLAUDE.md changes

Three new lines in ## ABSOLUTE RULES / ## SHARED STANDARDS:

  • The Linear-issues rule (wasn't in socket-cli's CLAUDE.md until now; exists in socket-repo-template/template/CLAUDE.md)
  • The new customer-name rule, pointing at Acme Inc
  • The new bugbot-reply rule with exact gh api invocation

All three rules exist even when the hook is not installed. The hook is the nudge, the CLAUDE.md is the binding rule.

Commits

  • 7d414c937chore(claude): add public-surface-reminder hook + customer-name rule
  • a9767af6cchore(claude): simplify placeholder to Acme Inc, add bugbot-reply rule

Source of truth

The same hook + rules landed on socket-repo-template main:

  • 876f42ffeat(template): add public-surface-reminder hook + customer-name rule
  • 23289aachore(template): simplify placeholder to Acme Inc, add bugbot-reply rule

so 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 0
  • Same with ls -la as the command — silent, exits 0
  • Same with gh api repos/X/Y/issues/1 -X GET — silent (read-only), exits 0
  • Same with gh api repos/X/Y/issues -X POST ... — prints reminder, exits 0
  • Same with gh pr create --title ... --body ... — prints reminder, exits 0

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.
@jdalton John-David Dalton (jdalton) changed the title chore(claude): add public-surface-reminder hook + customer-name rule chore(claude): public-surface-reminder hook + customer-name, Linear-issue, and bugbot-reply rules Apr 23, 2026
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.
Copy link
Copy Markdown

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 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.

@jdalton
Copy link
Copy Markdown
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants