Why this matters
The default system prompt is not a single giant string literal. It is built from named sections, and some of those sections are cached while others are deliberately allowed to change from turn to turn.
Big picture first
Read this page in three layers:
- the plain-English prompt text the model actually sees
- the
getSystemPrompt(...)builder that assembles the prompt list - the section registry helpers that decide what gets cached and what does not
The two prompt excerpts below are both real, but they are not adjacent in the assembled runtime prompt. Claude Code inserts several whole sections between them when getSystemPrompt(…) builds the final list.
You are an interactive agent that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
IMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.
IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files. # Tone and style
- Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.
- Your responses should be short and concise.
- When referencing specific functions or pieces of code include the pattern file_path:line_number to allow the user to easily navigate to the source code location.
- When referencing GitHub issues or pull requests, use the owner/repo#123 format (e.g. anthropics/claude-code#100) so they render as clickable links.
- Do not use a colon before tool calls. Your tool calls may not be shown directly in the output, so text like "Let me read the file:" followed by a read tool call should just be "Let me read the file." with a period. These excerpts matter because they show the source has distinct sections with different jobs. The intro sets the model’s overall posture, and the tone block adds style guidance later in the assembled prompt.
The builder
Here is the part of getSystemPrompt(...) that matters for this chapter:
export async function getSystemPrompt(
tools: Tools,
model: string,
additionalWorkingDirectories?: string[],
mcpClients?: MCPServerConnection[],
): Promise<string[]> {
const dynamicSections = [
systemPromptSection('session_guidance', () =>
getSessionSpecificGuidanceSection(enabledTools, skillToolCommands),
),
systemPromptSection('memory', () => loadMemoryPrompt()),
systemPromptSection('ant_model_override', () =>
getAntModelOverrideSection(),
),
systemPromptSection('env_info_simple', () =>
computeSimpleEnvInfo(model, additionalWorkingDirectories),
),
systemPromptSection('language', () =>
getLanguageSection(settings.language),
),
systemPromptSection('output_style', () =>
getOutputStyleSection(outputStyleConfig),
),
DANGEROUS_uncachedSystemPromptSection(
'mcp_instructions',
() =>
isMcpInstructionsDeltaEnabled()
? null
: getMcpInstructionsSection(mcpClients),
'MCP servers connect/disconnect between turns',
),
systemPromptSection('scratchpad', () => getScratchpadInstructions()),
systemPromptSection('frc', () => getFunctionResultClearingSection(model)),
systemPromptSection(
'summarize_tool_results',
() => SUMMARIZE_TOOL_RESULTS_SECTION,
),
]
const resolvedDynamicSections =
await resolveSystemPromptSections(dynamicSections)
return [
getSimpleIntroSection(outputStyleConfig),
getSimpleSystemSection(),
outputStyleConfig === null ||
outputStyleConfig.keepCodingInstructions === true
? getSimpleDoingTasksSection()
: null,
getActionsSection(),
getUsingYourToolsSection(enabledTools),
getSimpleToneAndStyleSection(),
getOutputEfficiencySection(),
...(shouldUseGlobalCacheScope() ? [SYSTEM_PROMPT_DYNAMIC_BOUNDARY] : []),
...resolvedDynamicSections,
].filter(s => s !== null)
The key idea is simple: the prompt starts with the static sections, then
optionally inserts SYSTEM_PROMPT_DYNAMIC_BOUNDARY, and only then appends the
resolved dynamic tail. That is why the chapter talks about “assembled
sections” instead of one monolithic prompt string.
The section registry
The helper file makes the caching model explicit:
type ComputeFn = () => string | null | Promise<string | null>
type SystemPromptSection = {
name: string
compute: ComputeFn
cacheBreak: boolean
}
export function systemPromptSection(
name: string,
compute: ComputeFn,
): SystemPromptSection {
return { name, compute, cacheBreak: false }
}
export function DANGEROUS_uncachedSystemPromptSection(
name: string,
compute: ComputeFn,
_reason: string,
): SystemPromptSection {
return { name, compute, cacheBreak: true }
}
export async function resolveSystemPromptSections(
sections: SystemPromptSection[],
): Promise<(string | null)[]> {
const cache = getSystemPromptSectionCache()
return Promise.all(
sections.map(async s => {
if (!s.cacheBreak && cache.has(s.name)) {
return cache.get(s.name) ?? null
}
const value = await s.compute()
setSystemPromptSectionCacheEntry(s.name, value)
return value
}),
)
}
systemPromptSection(...) means “cache this if you can.” The dangerous helper
means “recompute every turn on purpose.” resolveSystemPromptSections(...)
then turns the registry into the actual prompt fragments that getSystemPrompt
returns.
Why the boundary exists
SYSTEM_PROMPT_DYNAMIC_BOUNDARY is the line between stable prompt text and
volatile prompt text. The runtime uses it so the cache can treat the static
prefix differently from the dynamic tail. Put another way: the prompt is still
one assembled result, but not every piece of it has the same lifetime.
Takeaways
- The default system prompt is built from ordered sections, not handwritten as one block.
- The boundary marker separates the stable prefix from the dynamic tail that gets resolved later.
- Some sections are intentionally cached, while `DANGEROUS_uncachedSystemPromptSection(...)` opts out on purpose.