Stop Claude Code From Leaking Repo Details in Bug Reports
Telling Claude Code to leave out your organization name and file paths is a request, and requests get dropped mid-session. The fix that actually holds is a permission rule or a pre-tool hook on the command that posts the issue, not a nicer prompt. Whether the path is gh issue create, a GitHub MCP tool, or a browser session, each is a tool call Claude Code makes before anything reaches the public tracker, and every tool call in Claude Code can be intercepted before it runs.
This is a documented failure mode, not a hypothetical. A user asked Claude Code to draft a bug report for anthropics/claude-code's own public tracker and had to catch, by hand, private org names, private repo URLs, internal file paths, and infrastructure details before posting it (GitHub issue #29121). Nothing about that session was unusual. The agent had genuinely read those paths while reproducing the bug, and reproducing the bug is exactly what a good report needs.
Why the polite version doesn't work
The instinct is to add a line to CLAUDE.md saying to never include org names or internal file paths in content meant for public posting. Write it, and it will work most sessions. It's also not what CLAUDE.md is for. Anthropic's own docs on memory say plainly that CLAUDE.md content is "context, not enforced configuration," and that to block an action regardless of what Claude decides, you use a PreToolUse hook instead. A long session, a context compaction, or a rewritten instruction from three turns back can all make the model drop an instruction it read at the start, and there's no way to tell from the transcript that it happened until the draft is already sitting in a gh issue create command.
That's not a knock on the model. It's the same reason your team doesn't rely on "please remember not to force-push to main." You protect the branch instead.
Put a real gate on the submit step
A PreToolUse hook fires before Claude Code runs a tool, gets the tool name and full command on stdin, and can block the call outright by exiting 2. Anthropic's hooks reference is explicit that this exit code wins regardless of what Claude decided: it blocks whether or not the hook prints JSON, and as the docs put it, "even a JSON permissionDecision of "allow" can't override it." A minimal version for the gh CLI:
#!/bin/bash
# .claude/hooks/gate-public-post.sh
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // empty')
case "$cmd" in
"gh issue create"*|"gh pr create"*|"gh api "*"/issues"*)
echo "Blocked: this posts publicly. Paste the draft body here for me to check before it runs." >&2
exit 2
;;
esac
exit 0Wire it to PreToolUse for Bash in .claude/settings.json, and every attempt to actually submit stops for a human read, no matter how the intent to scrub got lost upstream. If your team files issues through a GitHub MCP server instead of the CLI, the same principle applies to that tool's create-issue call; a hook can match on tool name just as easily as on a Bash string.
Add the permission rule as a backstop
Hooks need someone to write and maintain them, so pair one with a plain deny or ask rule in .claude/settings.json. Anthropic's permissions reference documents the exact syntax, prefix-matched with a trailing wildcard:
{
"permissions": {
"ask": [
"Bash(gh issue create *)",
"Bash(gh pr create *)"
]
}
}An ask rule forces a manual approval prompt every time, showing you the full command (and the body text inside it) before it runs, even outside a session where you remembered to load the hook. Commit this file, and it applies to everyone on the repo, not just the person who happened to set it up on their own machine.
Read the draft like you'd read a diff
Once the gate is in place, the review itself is quick. Read the body before you approve. Look specifically for anything that names your org, a repo path, a hostname, or a file that isn't in the upstream project's own tree. Those are the same categories the original bug report called out. This is the same discipline as reading Claude Code's diff before you commit. The agent's stated intent ("I'll keep this generic") is not the artifact; the actual text in the tool call is.
What the hook actually catches
At Ironloop Robotics, a fleet-routing library the team depends on started hanging on graphs above a few thousand nodes. Farrukh Elmi, the backend engineer debugging it, had Claude Code reproduce the hang and draft an issue for the library's public tracker. Ironloop's ask rule on Bash(gh issue create *) did what it's there for: the command stopped before it ran, and the draft printed to the terminal instead of posting.
The draft it produced read in part:
Steps to reproduce:
- Clone
ironloop/fleet-router-internal- Run
python route_optimizer.py --config configs/prod-west-dc3.yaml- Graph loads from our internal warehouse-graph service at
10.4.2.18:7000; hang starts around node 40,000
None of that needed to leave Ironloop. Farrukh told Claude Code to strip everything above the library call itself and swap in a synthetic graph of the same size and shape, generated fresh so nothing in it traced back to Ironloop's repo name, config file, or internal host. Claude Code rewrote the reproduction as a self-contained script that built a 40,000-node graph matching the same degree distribution. That version is what got filed, and it's what got the library maintainers a same-day reply, because it was easier for them to run than the original would have been anyway.
Where the gate alone stops being enough
A hook and a permission rule solve the submission step, but they don't remove the manual step behind it. Farrukh's fix worked, and it also took him personally reading the draft, deciding what to strip, and describing the replacement graph by hand. That's a fine trade the first time it happens. It's a recurring cost if gh issue create keeps getting blocked because the agent keeps reaching into internal paths and Slack threads to answer something a person then has to keep re-answering by hand.
Modem's role here sits upstream of the hook, not in place of it. It reads Slack, support tools, and connected repos, dedupes the same request or bug across all of them, and keeps that as a queryable topic. Modem's Claude Code integration delegates tasks to Claude Code with descriptions already composed from bug reports, user quotes, and topic data, aggregated ahead of time rather than reconstructed by Claude Code from whatever it can find in one repo. Route issue-drafting through that path and there's less for Claude Code to go dig up in the first place, which means fewer drafts for the hook to catch and fewer times someone has to do what Farrukh did by hand. We build Modem, so read that recommendation knowing where it comes from. Below the point where this is a once-a-quarter annoyance rather than a weekly one, hooks and permission rules on gh issue create are the whole job, and they're free.
The smallest version you can set up today
Add the ask rule above to .claude/settings.json and commit it. That alone turns every gh issue create and gh pr create from Claude Code into a manual approval showing the full body, which is enough to catch what issue #29121 caught by hand. The hook script is the version that survives a teammate who dismisses the prompt without reading it; add it once the manual rule proves the gate is worth having.
