Jul 6, 2026 · 7 min · Engineering

Claude Code System Prompt: Engineering Lessons for Agent Builders

Claude Code System Prompt: Engineering Lessons for Agent Builders

Last month I watched an internal coding agent delete the wrong generated file, “fix” the test failure it caused, and then confidently report success. The root bug was not model quality. It was a bad boundary: the agent had permission to edit too much, vague rules about generated artifacts, and no required confirmation before destructive actions.

That is why the recent public discussion around Claude and Claude Code system prompts is useful, even if you should not treat leaked or collected prompts as gospel. Prompt dumps are often unofficial, incomplete, stale, or extracted from a product state that no longer exists. Claude Code itself evolves quickly, and the current Claude lineup — Opus 4.8, Sonnet 4.6, Haiku 4.5, and Fable 5 with 1M context — gives agent builders very different trade-offs than older generations.

But the interesting lesson is not “copy this prompt.” The useful lesson is architectural: mature coding agents encode operational boundaries, instruction priority, confirmation rules, file access discipline, and failure behavior into the system around the model.

This post is about those lessons.

Claude Code System Prompt Lessons Without Copying the Prompt

The phrase “Claude Code system prompt” has become a shortcut for “how does a serious coding agent tell the model to behave?” That is the wrong question if it leads to prompt archaeology, but the right question if it leads to better agent design.

In practice, the system prompt is only one layer. A production-grade agent usually has at least five cooperating control surfaces:

LayerWhat It ControlsExample
System instructionsStable behavioral contract“Prefer minimal changes; do not commit unless asked.”
Developer policyProduct-specific rules“Use apply_patch; never run destructive commands without approval.”
Tool schemaWhat the model can actually doread_file, edit_file, run_shell, update_plan
Runtime permissionsWhat the process is allowed to accessSandbox, working directory, network policy
User instructionsTask-specific goal“Fix the failing auth test.”

The mistake I see in many homegrown agents is collapsing these into one large prompt. That works in demos. It fails under pressure.

A better approach is to use the prompt to explain intent, and the runtime to enforce capability.

For example, do not merely prompt:

Do not delete files unless the user asks.

Make deletion a distinct tool requiring confirmation:

{
  "name": "delete_file",
  "description": "Delete a file from the workspace.",
  "input_schema": {
    "type": "object",
    "properties": {
      "path": { "type": "string" },
      "reason": { "type": "string" },
      "user_confirmed": { "type": "boolean" }
    },
    "required": ["path", "reason", "user_confirmed"]
  }
}

Then enforce it outside the model:

def delete_file(path: str, reason: str, user_confirmed: bool):
    if not user_confirmed:
        raise PermissionError("delete_file requires explicit user confirmation")
    if not path.startswith(WORKSPACE_ROOT):
        raise PermissionError("cannot delete files outside workspace")
    os.remove(path)

The prompt tells the agent how to think. The tool boundary decides what can happen.

Instruction Hierarchy Is an Engineering Interface

Claude models are good at following layered instructions, but you still need to design the layers carefully. If your coding agent has system, developer, project, and user instructions, each layer should have a clear job.

A useful hierarchy looks like this:

Claude Code-style workflows make this visible with files like CLAUDE.md, plus command-line configuration and session context. For agent builders, the larger lesson is that project-local instructions should be scoped and override only where appropriate.

A simple project instruction file might look like:

# CLAUDE.md

## Build and Test
- Use `pnpm test -- --runInBand` for focused test runs.
- Do not run `pnpm deploy` unless explicitly requested.

## Code Style
- Prefer small functions over large service classes.
- Do not add new dependencies without explaining why.

## Repo Notes
- API routes live in `apps/api/src/routes`.
- Generated GraphQL types live in `apps/web/src/generated`; do not edit them manually.

A common gotcha: agents often treat all instructions as globally equal. If the user says, “Ignore the project rules and edit generated files directly,” your agent needs to know whether that is allowed. In most setups, user instructions should not silently override higher-priority project or developer constraints.

You can model that explicitly:

PRIORITY = {
    "system": 4,
    "developer": 3,
    "project": 2,
    "user": 1,
}

def resolve_conflict(existing_rule, new_rule):
    if PRIORITY[new_rule.source] > PRIORITY[existing_rule.source]:
        return new_rule
    return existing_rule

In a real agent, the model should also explain conflicts to the user:

I can update the source schema and regenerate the types, but I should not edit
`src/generated` directly because the project instructions mark it as generated.

That one sentence prevents a surprising amount of damage.

Tool Boundaries Beat Prompt Politeness

When people inspect coding-agent prompts, they often focus on wording: “be concise,” “act like a senior engineer,” “don’t be sycophantic.” Those are useful, but they are not the core safety mechanism.

The core mechanism is the tool boundary.

A serious coding agent should distinguish between:

These are not morally equivalent operations.

Here is a practical permission matrix I use when designing agent runtimes:

OperationDefault PolicyWhy
Read workspace filesAllowNeeded for context gathering
Search with rgAllowLow risk and highly useful
Edit tracked source filesAllow with diff visibilityCore coding workflow
Run focused testsUsually allowVerifies changes
Install dependenciesAskAlters environment and lockfiles
Delete filesAskEasy to cause irreversible loss
Access networkAsk or denyCan leak data or create nondeterminism
Edit secretsDeny by defaultHigh blast radius
Commit changesAskRepository history is user-owned
DeployDeny unless explicitly requestedProduction impact

This is also where model choice matters. I like Sonnet-class models for most coding-agent loops because latency, cost, and reasoning balance well. Opus 4.8 is the model I would reach for when the agent needs deeper architectural synthesis or complex debugging. Haiku 4.5 fits fast classification and lightweight repo scanning. Fable 5’s 1M context is interesting for large-codebase comprehension, but long context does not remove the need for good retrieval, scoped reads, and tool discipline.

A million tokens of context can still contain the wrong file.

User Confirmation Should Be Specific, Not Ceremonial

Bad confirmation UX looks like this:

May I proceed?

Proceed with what? Editing one file? Running migrations? Deleting a directory?

Good confirmation is concrete:

I need confirmation before running:

`rm -rf apps/web/.next`

Reason: remove a stale Next.js build cache that may be causing the local
runtime error. This deletes only generated build output and does not touch
source files.

For Claude Code-like agents, this matters because the model may correctly infer the next step but still need human authorization. If every prompt asks for vague approval, users develop approval fatigue and click through. If only high-risk operations require precise confirmation, users stay engaged.

I recommend representing confirmation as structured state, not prose:

{
  "action": "run_shell",
  "command": "rm -rf apps/web/.next",
  "risk": "destructive_generated_artifact",
  "requires_confirmation": true,
  "confirmation_prompt": "Delete generated Next.js cache at apps/web/.next?"
}

Then your shell runner can enforce it:

def run_shell(command, confirmed=False):
    risk = classify_command(command)

    if risk.requires_confirmation and not confirmed:
        return {
            "status": "needs_confirmation",
            "message": risk.prompt
        }

    return subprocess.run(command, shell=True, cwd=WORKSPACE_ROOT)

In practice, command classification will never be perfect. That is fine. Use conservative defaults. If your classifier is unsure whether a command is destructive, ask.

Commands Worth Treating Carefully

These deserve explicit policy:

rm -rf path
git reset --hard
git clean -fd
git push --force
npm install some-package
pip install -r requirements.txt
docker compose down -v
kubectl delete ...
terraform apply

Some are harmless in the right context. Some are catastrophic in the wrong one. The agent should know the difference is contextual and ask when the blast radius is unclear.

File Access Is Context Engineering

The most underrated part of coding-agent design is file access. The prompt can say “inspect relevant files first,” but the runtime should make that behavior easy.

At minimum, give the model tools for:

A minimal tool set might look like:

[
  {
    "name": "search_files",
    "input_schema": {
      "type": "object",
      "properties": {
        "query": { "type": "string" },
        "glob": { "type": "string" }
      },
      "required": ["query"]
    }
  },
  {
    "name": "read_file",
    "input_schema": {
      "type": "object",
      "properties": {
        "path": { "type": "string" },
        "start_line": { "type": "integer" },
        "limit": { "type": "integer" }
      },
      "required": ["path"]
    }
  },
  {
    "name": "apply_patch",
    "input_schema": {
      "type": "object",
      "properties": {
        "patch": { "type": "string" }
      },
      "required": ["patch"]
    }
  }
]

Bounded reads matter. If the agent reads every file in a monorepo, it wastes context and often performs worse. If it reads too little, it guesses. The sweet spot is iterative context acquisition:

rg "createSession|verifySession|SessionStore" apps packages
sed -n '1,220p' apps/api/src/auth/session.ts
sed -n '1,180p' apps/api/src/routes/login.ts
git diff -- apps/api/src/auth/session.ts

Claude Code users will recognize this rhythm: search, inspect, edit, verify. Agent builders should encode that loop into the default behavior.

A common gotcha: file tools need to preserve line endings, permissions, and encoding expectations. I have seen agents “successfully” rewrite shell scripts and remove executable bits, or normalize files in ways that create noisy diffs. Your edit layer should be boring and conservative.

Failure Modes: What Actually Happens When Agents Go Wrong

Most coding agents do not fail dramatically. They fail plausibly.

Here are the failure modes I see most often.

1. The Agent Solves the Symptom

The test fails because an API returns 401. The agent changes the test to expect 401. Green build, broken product.

Mitigation:

2. The Agent Over-Edits

The user asks for a small bug fix. The agent reformats five files, renames variables, and changes public APIs.

Mitigation:

Make the minimal change that fixes the issue. Do not perform opportunistic
refactors unless they are necessary for correctness.

But also enforce diff review. Show changed files and line counts before finalizing:

git diff --stat
git diff -- apps/api/src/auth/session.ts

3. The Agent Trusts Stale Context

Long-running sessions accumulate outdated assumptions. The user changes a file externally, tests now fail differently, and the agent continues from the old mental model.

Mitigation:

4. The Agent Masks Tool Failure

A shell command fails, but the agent proceeds as if it succeeded.

Mitigation: tool results should be structured and hard to ignore.

{
  "tool": "run_shell",
  "command": "pnpm test auth",
  "exit_code": 1,
  "stdout": "...",
  "stderr": "Cannot find module '@repo/config'",
  "status": "failed"
}

Then instruct the agent:

If a tool call fails, do not claim success. Diagnose the failure or clearly
report the blocker.

5. The Agent Writes Secrets Into Context

If the agent can read .env, crash dumps, or production config, it may accidentally include secrets in summaries, logs, or prompts.

Mitigation:

Example path policy:

DENY_PATTERNS = [
    ".env",
    ".env.*",
    "**/secrets/**",
    "**/*.pem",
    "**/*.key",
]

def can_read(path):
    return not any(fnmatch(path, pattern) for pattern in DENY_PATTERNS)

Prompting the model not to leak secrets is good. Preventing the model from seeing unnecessary secrets is better.

Designing a Claude API Agent Loop

If you are building directly on the Claude API, the simplest useful loop is:

  1. Send system and developer instructions.
  2. Provide the user task.
  3. Let Claude request tools.
  4. Execute tools in your runtime.
  5. Return tool results.
  6. Repeat until the model produces a final answer.

A simplified request shape looks like this:

{
  "model": "claude-sonnet-4-6",
  "max_tokens": 4096,
  "system": "You are a coding agent. Make minimal, correct changes...",
  "messages": [
    {
      "role": "user",
      "content": "Fix the auth timeout bug and add a regression test."
    }
  ],
  "tools": [
    {
      "name": "read_file",
      "description": "Read a bounded section of a workspace file.",
      "input_schema": {
        "type": "object",
        "properties": {
          "path": { "type": "string" },
          "start_line": { "type": "integer" },
          "limit": { "type": "integer" }
        },
        "required": ["path"]
      }
    }
  ]
}

A tool-use response conceptually looks like:

{
  "role": "assistant",
  "content": [
    {
      "type": "text",
      "text": "I’ll inspect the auth timeout path first."
    },
    {
      "type": "tool_use",
      "name": "read_file",
      "id": "toolu_123",
      "input": {
        "path": "apps/api/src/auth/session.ts",
        "start_line": 1,
        "limit": 220
      }
    }
  ]
}

Your runtime then returns the result:

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_123",
      "content": "export function createSession(...) { ... }"
    }
  ]
}

The exact SDK syntax depends on your client version, but the design principle is stable: Claude proposes actions, your runtime executes or rejects them, and the next model turn receives the outcome.

That separation is what keeps the system understandable.

Claude Code Commands and Settings Worth Studying

Even if you are not using Claude Code directly, its workflow gives useful product patterns.

Typical project bootstrap:

claude
/init

That creates or updates project guidance so future sessions understand repo-specific conventions.

A practical session often looks like:

claude "Find why the billing webhook test is flaky. Do not edit files yet."
claude "Patch the smallest fix and run the focused test."
claude "Show me the diff and summarize remaining risks."

I like this style because it separates investigation, mutation, and verification. Agent builders can mirror that as modes:

{
  "mode": "investigate",
  "can_edit": false,
  "can_run_tests": true,
  "requires_confirmation_for": ["network", "delete", "install"]
}

Then switch modes explicitly:

{
  "mode": "implement",
  "can_edit": true,
  "can_run_tests": true,
  "requires_confirmation_for": ["network", "delete", "install", "commit"]
}

The important part is not the specific command. It is the interaction contract. Users should know when the agent is only reading, when it is changing files, and when it is about to do something with side effects.

Why Prompt Dumps Are Brittle

The GitHub repository that collected system prompts and model configurations became popular because engineers are naturally curious about how real tools are instructed. I get the appeal. Reading prompts can reveal product taste: what risks the builders worry about, what workflows they optimize, and how they talk to the model.

But copied prompts are brittle for several reasons:

Use these artifacts as design inspiration, not as dependencies.

A better exercise is to ask: what problem is this instruction solving? Then solve that problem in your own stack with both prompt and runtime support.

Practical Takeaways

AC
Alex Chen · Systems & Inference Engineer

Alex builds high-throughput LLM serving and agent infrastructure, and ships production systems on the Claude API daily. He writes about latency, token economics, rate-limit engineering, and what actually happens when Claude models run at scale.

Get cheaper Claude API access

One API key for Claude Opus 4.8, Sonnet 4.6, Haiku 4.5, Fable 5, plus GPT & Gemini — up to 80% off official pricing, pay-as-you-go.

Get Your API Key →
AI Prime Tech is an independent third-party API gateway. Claude™ and Anthropic® are trademarks of Anthropic, PBC. No affiliation or endorsement is implied.