90 lines
3.6 KiB
TypeScript
90 lines
3.6 KiB
TypeScript
/**
|
|
* Undercover mode — safety utilities for contributing to public/open-source repos.
|
|
*
|
|
* When active, Claude Code adds safety instructions to commit/PR prompts and
|
|
* strips all attribution to avoid leaking internal model codenames, project
|
|
* names, or other Anthropic-internal information. The model is not told what
|
|
* model it is.
|
|
*
|
|
* Activation:
|
|
* - CLAUDE_CODE_UNDERCOVER=1 — force ON (even in internal repos)
|
|
* - Otherwise AUTO: active UNLESS the repo remote matches the internal
|
|
* allowlist (INTERNAL_MODEL_REPOS in commitAttribution.ts). Safe default
|
|
* is ON — Claude may push to public remotes from a CWD that isn't itself
|
|
* a git checkout (e.g. /tmp crash repro).
|
|
* - There is NO force-OFF. This guards against model codename leaks — if
|
|
* we're not confident we're in an internal repo, we stay undercover.
|
|
*
|
|
* All code paths are gated on process.env.USER_TYPE === 'ant'. Since USER_TYPE is
|
|
* a build-time --define, the bundler constant-folds these checks and dead-code-
|
|
* eliminates the ant-only branches from external builds. In external builds every
|
|
* function in this file reduces to a trivial return.
|
|
*/
|
|
|
|
import { getRepoClassCached } from './commitAttribution.js'
|
|
import { getGlobalConfig } from './config.js'
|
|
import { isEnvTruthy } from './envUtils.js'
|
|
|
|
export function isUndercover(): boolean {
|
|
if (process.env.USER_TYPE === 'ant') {
|
|
if (isEnvTruthy(process.env.CLAUDE_CODE_UNDERCOVER)) return true
|
|
// Auto: active unless we've positively confirmed we're in an allowlisted
|
|
// internal repo. 'external', 'none', and null (check not yet run) all
|
|
// resolve to ON. The check is primed in setup.ts; only 'internal' → OFF.
|
|
return getRepoClassCached() !== 'internal'
|
|
}
|
|
return false
|
|
}
|
|
|
|
export function getUndercoverInstructions(): string {
|
|
if (process.env.USER_TYPE === 'ant') {
|
|
return `## UNDERCOVER MODE — CRITICAL
|
|
|
|
You are operating UNDERCOVER in a PUBLIC/OPEN-SOURCE repository. Your commit
|
|
messages, PR titles, and PR bodies MUST NOT contain ANY Anthropic-internal
|
|
information. Do not blow your cover.
|
|
|
|
NEVER include in commit messages or PR descriptions:
|
|
- Internal model codenames (animal names like Capybara, Tengu, etc.)
|
|
- Unreleased model version numbers (e.g., opus-4-7, sonnet-4-8)
|
|
- Internal repo or project names (e.g., claude-cli-internal, anthropics/…)
|
|
- Internal tooling, Slack channels, or short links (e.g., go/cc, #claude-code-…)
|
|
- The phrase "Claude Code" or any mention that you are an AI
|
|
- Any hint of what model or version you are
|
|
- Co-Authored-By lines or any other attribution
|
|
|
|
Write commit messages as a human developer would — describe only what the code
|
|
change does.
|
|
|
|
GOOD:
|
|
- "Fix race condition in file watcher initialization"
|
|
- "Add support for custom key bindings"
|
|
- "Refactor parser for better error messages"
|
|
|
|
BAD (never write these):
|
|
- "Fix bug found while testing with Claude Capybara"
|
|
- "1-shotted by claude-opus-4-6"
|
|
- "Generated with Claude Code"
|
|
- "Co-Authored-By: Claude Opus 4.6 <…>"
|
|
`
|
|
}
|
|
return ''
|
|
}
|
|
|
|
/**
|
|
* Check whether to show the one-time explainer dialog for auto-undercover.
|
|
* True when: undercover is active via auto-detection (not forced via env),
|
|
* and the user hasn't seen the notice before. Pure — the component marks the
|
|
* flag on mount.
|
|
*/
|
|
export function shouldShowUndercoverAutoNotice(): boolean {
|
|
if (process.env.USER_TYPE === 'ant') {
|
|
// If forced via env, user already knows; don't nag.
|
|
if (isEnvTruthy(process.env.CLAUDE_CODE_UNDERCOVER)) return false
|
|
if (!isUndercover()) return false
|
|
if (getGlobalConfig().hasSeenUndercoverAutoNotice) return false
|
|
return true
|
|
}
|
|
return false
|
|
}
|