Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
11 kB · 278 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279import memoize from 'lodash-es/memoize.js'import { homedir } from 'os'import { isAbsolute, join, normalize, sep } from 'path'import { getIsNonInteractiveSession, getProjectRoot,} from '../bootstrap/state.js'import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js'import { getClaudeConfigHomeDir, isEnvDefinedFalsy, isEnvTruthy,} from '../utils/envUtils.js'import { findCanonicalGitRoot } from '../utils/git.js'import { sanitizePath } from '../utils/path.js'import { getInitialSettings, getSettingsForSource,} from '../utils/settings/settings.js'
/** * Whether auto-memory features are enabled (memdir, agent memory, past session search). * Enabled by default. Priority chain (first defined wins): * 1. CLAUDE_CODE_DISABLE_AUTO_MEMORY env var (1/true → OFF, 0/false → ON) * 2. CLAUDE_CODE_SIMPLE (--bare) → OFF * 3. CCR without persistent storage → OFF (no CLAUDE_CODE_REMOTE_MEMORY_DIR) * 4. autoMemoryEnabled in settings.json (supports project-level opt-out) * 5. Default: enabled */export function isAutoMemoryEnabled(): boolean { const envVal = process.env.CLAUDE_CODE_DISABLE_AUTO_MEMORY if (isEnvTruthy(envVal)) { return false } if (isEnvDefinedFalsy(envVal)) { return true } // --bare / SIMPLE: prompts.ts already drops the memory section from the // system prompt via its SIMPLE early-return; this gate stops the other half // (extractMemories turn-end fork, autoDream, /remember, /dream, team sync). if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) { return false } if ( isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) && !process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR ) { return false } const settings = getInitialSettings() if (settings.autoMemoryEnabled !== undefined) { return settings.autoMemoryEnabled } return true}
/** * Whether the extract-memories background agent will run this session. * * The main agent's prompt always has full save instructions regardless of * this gate — when the main agent writes memories, the background agent * skips that range (hasMemoryWritesSince in extractMemories.ts); when it * doesn't, the background agent catches anything missed. * * Callers must also gate on feature('EXTRACT_MEMORIES') — that check cannot * live inside this helper because feature() only tree-shakes when used * directly in an `if` condition. */export function isExtractModeActive(): boolean { if (!getFeatureValue_CACHED_MAY_BE_STALE('tengu_passport_quail', false)) { return false } return ( !getIsNonInteractiveSession() || getFeatureValue_CACHED_MAY_BE_STALE('tengu_slate_thimble', false) )}
/** * Returns the base directory for persistent memory storage. * Resolution order: * 1. CLAUDE_CODE_REMOTE_MEMORY_DIR env var (explicit override, set in CCR) * 2. ~/.claude (default config home) */export function getMemoryBaseDir(): string { if (process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR) { return process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR } return getClaudeConfigHomeDir()}
const AUTO_MEM_DIRNAME = 'memory'const AUTO_MEM_ENTRYPOINT_NAME = 'MEMORY.md'
/** * Normalize and validate a candidate auto-memory directory path. * * SECURITY: Rejects paths that would be dangerous as a read-allowlist root * or that normalize() doesn't fully resolve: * - relative (!isAbsolute): "../foo" — would be interpreted relative to CWD * - root/near-root (length < 3): "/" → "" after strip; "/a" too short * - Windows drive-root (C: regex): "C:\" → "C:" after strip * - UNC paths (\\server\share): network paths — opaque trust boundary * - null byte: survives normalize(), can truncate in syscalls * * Returns the normalized path with exactly one trailing separator, * or undefined if the path is unset/empty/rejected. */function validateMemoryPath( raw: string | undefined, expandTilde: boolean,): string | undefined { if (!raw) { return undefined } let candidate = raw // Settings.json paths support ~/ expansion (user-friendly). The env var // override does not (it's set programmatically by Cowork/SDK, which should // always pass absolute paths). Bare "~", "~/", "~/.", "~/..", etc. are NOT // expanded — they would make isAutoMemPath() match all of $HOME or its // parent (same class of danger as "/" or "C:\"). if ( expandTilde && (candidate.startsWith('~/') || candidate.startsWith('~\\')) ) { const rest = candidate.slice(2) // Reject trivial remainders that would expand to $HOME or an ancestor. // normalize('') = '.', normalize('.') = '.', normalize('foo/..') = '.', // normalize('..') = '..', normalize('foo/../..') = '..' const restNorm = normalize(rest || '.') if (restNorm === '.' || restNorm === '..') { return undefined } candidate = join(homedir(), rest) } // normalize() may preserve a trailing separator; strip before adding // exactly one to match the trailing-sep contract of getAutoMemPath() const normalized = normalize(candidate).replace(/[/\\]+$/, '') if ( !isAbsolute(normalized) || normalized.length < 3 || /^[A-Za-z]:$/.test(normalized) || normalized.startsWith('\\\\') || normalized.startsWith('//') || normalized.includes('\0') ) { return undefined } return (normalized + sep).normalize('NFC')}
/** * Direct override for the full auto-memory directory path via env var. * When set, getAutoMemPath()/getAutoMemEntrypoint() return this path directly * instead of computing `{base}/projects/{sanitized-cwd}/memory/`. * * Used by Cowork to redirect memory to a space-scoped mount where the * per-session cwd (which contains the VM process name) would otherwise * produce a different project-key for every session. */function getAutoMemPathOverride(): string | undefined { return validateMemoryPath( process.env.CLAUDE_COWORK_MEMORY_PATH_OVERRIDE, false, )}
/** * Settings.json override for the full auto-memory directory path. * Supports ~/ expansion for user convenience. * * SECURITY: projectSettings (.claude/settings.json committed to the repo) is * intentionally excluded — a malicious repo could otherwise set * autoMemoryDirectory: "~/.ssh" and gain silent write access to sensitive * directories via the filesystem.ts write carve-out (which fires when * isAutoMemPath() matches and hasAutoMemPathOverride() is false). This follows * the same pattern as hasSkipDangerousModePermissionPrompt() etc. */function getAutoMemPathSetting(): string | undefined { const dir = getSettingsForSource('policySettings')?.autoMemoryDirectory ?? getSettingsForSource('flagSettings')?.autoMemoryDirectory ?? getSettingsForSource('localSettings')?.autoMemoryDirectory ?? getSettingsForSource('userSettings')?.autoMemoryDirectory return validateMemoryPath(dir, true)}
/** * Check if CLAUDE_COWORK_MEMORY_PATH_OVERRIDE is set to a valid override. * Use this as a signal that the SDK caller has explicitly opted into * the auto-memory mechanics — e.g. to decide whether to inject the * memory prompt when a custom system prompt replaces the default. */export function hasAutoMemPathOverride(): boolean { return getAutoMemPathOverride() !== undefined}
/** * Returns the canonical git repo root if available, otherwise falls back to * the stable project root. Uses findCanonicalGitRoot so all worktrees of the * same repo share one auto-memory directory (anthropics/claude-code#24382). */function getAutoMemBase(): string { return findCanonicalGitRoot(getProjectRoot()) ?? getProjectRoot()}
/** * Returns the auto-memory directory path. * * Resolution order: * 1. CLAUDE_COWORK_MEMORY_PATH_OVERRIDE env var (full-path override, used by Cowork) * 2. autoMemoryDirectory in settings.json (trusted sources only: policy/local/user) * 3. <memoryBase>/projects/<sanitized-git-root>/memory/ * where memoryBase is resolved by getMemoryBaseDir() * * Memoized: render-path callers (collapseReadSearchGroups → isAutoManagedMemoryFile) * fire per tool-use message per Messages re-render; each miss costs * getSettingsForSource × 4 → parseSettingsFile (realpathSync + readFileSync). * Keyed on projectRoot so tests that change its mock mid-block recompute; * env vars / settings.json / CLAUDE_CONFIG_DIR are session-stable in * production and covered by per-test cache.clear. */export const getAutoMemPath = memoize( (): string => { const override = getAutoMemPathOverride() ?? getAutoMemPathSetting() if (override) { return override } const projectsDir = join(getMemoryBaseDir(), 'projects') return ( join(projectsDir, sanitizePath(getAutoMemBase()), AUTO_MEM_DIRNAME) + sep ).normalize('NFC') }, () => getProjectRoot(),)
/** * Returns the daily log file path for the given date (defaults to today). * Shape: <autoMemPath>/logs/YYYY/MM/YYYY-MM-DD.md * * Used by assistant mode (feature('KAIROS')): rather than maintaining * MEMORY.md as a live index, the agent appends to a date-named log file * as it works. A separate nightly /dream skill distills these logs into * topic files + MEMORY.md. */export function getAutoMemDailyLogPath(date: Date = new Date()): string { const yyyy = date.getFullYear().toString() const mm = (date.getMonth() + 1).toString().padStart(2, '0') const dd = date.getDate().toString().padStart(2, '0') return join(getAutoMemPath(), 'logs', yyyy, mm, `${yyyy}-${mm}-${dd}.md`)}
/** * Returns the auto-memory entrypoint (MEMORY.md inside the auto-memory dir). * Follows the same resolution order as getAutoMemPath(). */export function getAutoMemEntrypoint(): string { return join(getAutoMemPath(), AUTO_MEM_ENTRYPOINT_NAME)}
/** * Check if an absolute path is within the auto-memory directory. * * When CLAUDE_COWORK_MEMORY_PATH_OVERRIDE is set, this matches against the * env-var override directory. Note that a true return here does NOT imply * write permission in that case — the filesystem.ts write carve-out is gated * on !hasAutoMemPathOverride() (it exists to bypass DANGEROUS_DIRECTORIES). * * The settings.json autoMemoryDirectory DOES get the write carve-out: it's the * user's explicit choice from a trusted settings source (projectSettings is * excluded — see getAutoMemPathSetting), and hasAutoMemPathOverride() remains * false for it. */export function isAutoMemPath(absolutePath: string): boolean { // SECURITY: Normalize to prevent path traversal bypasses via .. segments const normalizedPath = normalize(absolutePath) return normalizedPath.startsWith(getAutoMemPath())}