Why this matters
Before Claude Code can build a system prompt, it snapshots a few plain-text facts about the repo and the session. The important part is that these memoized helpers are cached for the conversation until their caches are cleared, so Claude Code does not keep rereading the same inputs over and over during the conversation.
Big picture first
This page explains how getGitStatus(), getSystemContext(), and
getUserContext() build cached snapshots instead of live-updating state.
getSystemContext() and getUserContext() both return plain objects. Those
keys become named context sections later in prompt assembly, so it helps to
see the shape before the code that fills them in.
The two context objects
Both helpers return plain JavaScript objects whose keys become named context sections later in prompt assembly.
| Field | Meaning | First Pass |
|---|---|---|
gitStatus | A frozen text snapshot of branch name, status, and recent commits. | Only appears when git instructions are enabled and the repo is actually a git repo. |
cacheBreaker | An optional debug-only string used to invalidate cached context on demand. | You can ignore this on the first read unless you are studying cache invalidation. |
claudeMd | The combined CLAUDE.md-style instruction text loaded from memory files. | This is the bridge from instruction files to user-visible prompt text. |
currentDate | A one-line natural-language date string added to every user context snapshot. | This is the easiest example of a tiny but useful context field. |
The four fields that matter most here are gitStatus, cacheBreaker,
claudeMd, and currentDate.
const MAX_STATUS_CHARS = 2000
export const getGitStatus = memoize(async (): Promise<string | null> => {
if (process.env.NODE_ENV === 'test') {
// Avoid cycles in tests
return null
}
const startTime = Date.now()
logForDiagnosticsNoPII('info', 'git_status_started')
const isGitStart = Date.now()
const isGit = await getIsGit()
logForDiagnosticsNoPII('info', 'git_is_git_check_completed', {
duration_ms: Date.now() - isGitStart,
is_git: isGit,
})
if (!isGit) {
logForDiagnosticsNoPII('info', 'git_status_skipped_not_git', {
duration_ms: Date.now() - startTime,
})
return null
}
try {
const gitCmdsStart = Date.now()
const [branch, mainBranch, status, log, userName] = await Promise.all([
getBranch(),
getDefaultBranch(),
execFileNoThrow(gitExe(), ['--no-optional-locks', 'status', '--short'], {
preserveOutputOnError: false,
}).then(({ stdout }) => stdout.trim()),
execFileNoThrow(
gitExe(),
['--no-optional-locks', 'log', '--oneline', '-n', '5'],
{
preserveOutputOnError: false,
},
).then(({ stdout }) => stdout.trim()),
execFileNoThrow(gitExe(), ['config', 'user.name'], {
preserveOutputOnError: false,
}).then(({ stdout }) => stdout.trim()),
])
logForDiagnosticsNoPII('info', 'git_commands_completed', {
duration_ms: Date.now() - gitCmdsStart,
status_length: status.length,
})
// Check if status exceeds character limit
const truncatedStatus =
status.length > MAX_STATUS_CHARS
? status.substring(0, MAX_STATUS_CHARS) +
'\n... (truncated because it exceeds 2k characters. If you need more information, run "git status" using BashTool)'
: status
logForDiagnosticsNoPII('info', 'git_status_completed', {
duration_ms: Date.now() - startTime,
truncated: status.length > MAX_STATUS_CHARS,
})
return [
`This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation.`,
`Current branch: ${branch}`,
`Main branch (you will usually use this for PRs): ${mainBranch}`,
...(userName ? [`Git user: ${userName}`] : []),
`Status:\n${truncatedStatus || '(clean)'}`,
`Recent commits:\n${log}`,
].join('\n\n')
} catch (error) {
logForDiagnosticsNoPII('error', 'git_status_failed', {
duration_ms: Date.now() - startTime,
})
logError(error)
return null
}
})
export const getSystemContext = memoize(
async (): Promise<{
[k: string]: string
}> => {
const startTime = Date.now()
logForDiagnosticsNoPII('info', 'system_context_started')
// Skip git status in CCR (unnecessary overhead on resume) or when git instructions are disabled
const gitStatus =
isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) ||
!shouldIncludeGitInstructions()
? null
: await getGitStatus()
// Include system prompt injection if set (for cache breaking, ant-only)
const injection = feature('BREAK_CACHE_COMMAND')
? getSystemPromptInjection()
: null
logForDiagnosticsNoPII('info', 'system_context_completed', {
duration_ms: Date.now() - startTime,
has_git_status: gitStatus !== null,
has_injection: injection !== null,
})
return {
...(gitStatus && { gitStatus }),
...(feature('BREAK_CACHE_COMMAND') && injection
? {
cacheBreaker: `[CACHE_BREAKER: ${injection}]`,
}
: {}),
}
},
)
export const getUserContext = memoize(
async (): Promise<{
[k: string]: string
}> => {
const startTime = Date.now()
logForDiagnosticsNoPII('info', 'user_context_started')
// CLAUDE_CODE_DISABLE_CLAUDE_MDS: hard off, always.
// --bare: skip auto-discovery (cwd walk), BUT honor explicit --add-dir.
// --bare means "skip what I didn't ask for", not "ignore what I asked for".
const shouldDisableClaudeMd =
isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_CLAUDE_MDS) ||
(isBareMode() && getAdditionalDirectoriesForClaudeMd().length === 0)
// Await the async I/O (readFile/readdir directory walk) so the event
// loop yields naturally at the first fs.readFile.
const claudeMd = shouldDisableClaudeMd
? null
: getClaudeMds(filterInjectedMemoryFiles(await getMemoryFiles()))
// Cache for the auto-mode classifier (yoloClassifier.ts reads this
// instead of importing claudemd.ts directly, which would create a
// cycle through permissions/filesystem → permissions → yoloClassifier).
setCachedClaudeMdContent(claudeMd || null)
logForDiagnosticsNoPII('info', 'user_context_completed', {
duration_ms: Date.now() - startTime,
claudemd_length: claudeMd?.length ?? 0,
claudemd_disabled: Boolean(shouldDisableClaudeMd),
})
return {
...(claudeMd && { claudeMd }),
currentDate: `Today's date is ${getLocalISODate()}.`,
}
},
)
Takeaways
- Context snapshots are gathered before the final prompt is assembled.
- The memoized helpers stay stable for the conversation until their caches are cleared.
- The user context carries the date and instruction text, not just git state.