I asked Claude Code to refactor our authentication module. Ten minutes later: clean code, proper error handling, tests included. The hype is real.

Then reality hit. I wanted to use Claude Code on actual production projects. Every real project has secrets:

  • .env files with database URLs and API keys
  • AWS/GCP/Azure credentials
  • Stripe/payment provider keys
  • OAuth client secrets
  • Private SSL certificates
  • Proprietary business logic (trading algorithms, pricing engines)
  • docker-compose.yml with production passwords
  • Firebase/Supabase config files

The naive approach — create a blank demo project, copy files over, ask Claude Code to edit them, copy back — is perfectly safe. It’s also perfectly miserable. I tried it for a week and wanted to throw my laptop out the window. So I tested four strategies over the last three months. Here’s what actually works.


What Claude Code Actually Sees

First, let’s clear up a common misconception. Claude Code doesn’t automatically scan your entire project. It requests file access. You approve or deny. This is good.

The problem is that the boundaries aren’t clean. When Claude Code needs architectural context, it naturally wants to read files near your sensitive code. The interface might be harmless, but the implementation contains database URLs, auth headers, and API keys.

Example:

# These files are SAFE for Claude Code to read:
src/services/user.service.ts # TypeScript interface
app/api/users/route.ts # Next.js API route
lib/repositories/user_repo.dart # Flutter repository
Sources/Services/UserService.swift # iOS service protocol
# These files contain SECRETS — danger zone:
.env # DATABASE_URL, API keys
docker-compose.yml # DB passwords, service tokens
src/config/database.ts # Connection strings
firebase.json # Firebase config
google-services.json # Android Firebase
GoogleService-Info.plist # iOS Firebase

Here’s what it looks like in code across multiple languages:

// src/services/payment.service.ts — harmless interface ✅
export interface PaymentService {
charge(amount: number, currency: string): Promise<PaymentResult>;
}
// src/config/stripe.ts — contains secrets ❌
export const stripeConfig = {
secretKey: process.env.STRIPE_SECRET_KEY, // real key at runtime
webhookSecret: "whsec_live_abc123...", // hardcoded! danger
};
# services/payment_service.py - harmless interface ✅
class PaymentService:
def charge(self, amount: float, currency: str) -> PaymentResult:
pass
# config/stripe.py - contains secrets ❌
STRIPE_SECRET_KEY = "sk_live_51J..." # danger
STRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET", "whsec_live_abc123")
// Services/PaymentService.swift - harmless protocol ✅
protocol PaymentService {
func charge(amount: Decimal, currency: String) async -> PaymentResult
}
// Config/StripeConfig.swift - contains secrets ❌
struct StripeConfig {
static let secretKey = "sk_live_51J..." // danger
static let webhookSecret = "whsec_live_abc123..."
}

Claude Code asks to read payment.service.ts for context. Seems fine. Then it asks for stripe.ts because that’s where configuration lives. Now it has your secrets.

The question isn’t whether Claude Code will see sensitive code. It’s when and how much you’re comfortable exposing. Here are four strategies I’ve used, ranked by security and daily sustainability.


Strategy 1: Demo Project + Copy-Paste

How it works:

Real Project (secret files)
↓ manual copy
Blank Demo Project (no secrets) → Claude Code works here
↓ manual copy back
Real Project (updated)

Create a blank project with no secrets. Copy the files you want to edit into it. Use Claude Code there. Copy the edited files back to your real project.

The experience:

This is maximum security. Claude Code never touches your real project. The problem is that it’s miserable to use.

I tried this for a week. I needed Claude Code to refactor an auth module that depends on five services, two middleware files, and a database config. I copy-pasted seven files into the demo project. Claude Code asked for context about the base service class. I copy-pasted that. Then it needed the error handler. I copy-pasted that. Then it needed the middleware chain setup.

Thirty minutes later, I’d copy-pasted 15 files, Claude Code gave me a refactor that didn’t compile because it was missing context from three other files I didn’t copy, and I spent another 45 minutes manually merging changes back into the real project.

A task that should have taken five minutes took 90. The output quality was mediocre because Claude Code was working with context poverty — snippets without the full architectural picture.

Verdict:

SecuritySpeedOutput QualityDaily Sustainability
⭐⭐⭐⭐⭐⭐⭐

Best for: One-off edits on a single isolated file with zero dependencies. Not realistic for most real work.


Strategy 2: Duplicate Project + Git Patch

How it works:

git clone real-project duplicated-project
cd duplicated-project
./remove-secrets.sh # automated script
./add-dummy-secrets.sh # automated script
# work with Claude Code here
git diff > changes.patch
cd ../real-project
git apply changes.patch

Clone your real project. Run a script that removes all secret files and replaces constants with dummy values. Work with Claude Code in this clean duplicate. When done, generate a git patch of the changes and apply it to the real project.

The experience:

This was a massive improvement over copy-paste. I wrote a universal cleanup script:

#!/bin/bash
# remove-secrets.sh (works for any project)
# Remove environment files
rm -f .env .env.local .env.production .env.*.local
# Remove infrastructure secrets
rm -f docker-compose.prod.yml docker-compose.override.yml
# Remove cloud credentials
rm -f **/credentials.json **/service-account.json
rm -rf ~/.aws/ .aws/
# Remove certificates and keys
find . -name "*.pem" -delete
find . -name "*.key" -delete
find . -name "*.p12" -delete
find . -name "*.keystore" -delete
find . -name "*.jks" -delete
# Remove platform-specific secrets
find . -name "google-services.json" -delete
find . -name "GoogleService-Info.plist" -delete
rm -f local.properties local.settings.json
# Replace hardcoded secrets with dummies (adapt patterns to your stack)
# TypeScript/JavaScript
find . -name "*.ts" -o -name "*.js" | xargs sed -i '' 's/sk_live_[a-zA-Z0-9]*/sk_test_DUMMY_KEY/g'
find . -name "*.ts" -o -name "*.js" | xargs sed -i '' 's/https:\/\/api\.internal\./https:\/\/api.example.com\//g'
# Python
find . -name "*.py" | xargs sed -i '' 's/sk_live_[a-zA-Z0-9]*/sk_test_DUMMY_KEY/g'
# Go
find . -name "*.go" | xargs sed -i '' 's/sk_live_[a-zA-Z0-9]*/sk_test_DUMMY_KEY/g'
echo "✅ Secrets removed"
add-dummy-secrets.sh
#!/bin/bash
# Create dummy .env
cat > .env << EOF
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
REDIS_URL=redis://localhost:6379
STRIPE_SECRET_KEY=sk_test_DUMMY
STRIPE_WEBHOOK_SECRET=whsec_test_DUMMY
JWT_SECRET=dummy-secret-for-dev
AWS_ACCESS_KEY_ID=AKIADUMMY
AWS_SECRET_ACCESS_KEY=dummy
GOOGLE_OAUTH_CLIENT_SECRET=GOCSPX-dummy
EOF
# Create dummy docker-compose.yml
cat > docker-compose.yml << EOF
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: dummy
POSTGRES_DB: mydb
EOF
# Platform-specific dummies
# Android
if [ -d "android" ]; then
echo "STRIPE_KEY=pk_test_DUMMY" > local.properties
echo '{"project_info":{"project_id":"dummy"}}' > google-services.json
fi
# iOS
if [ -d "ios" ]; then
echo '<?xml version="1.0" encoding="UTF-8"?><plist><dict><key>API_KEY</key><string>DUMMY</string></dict></plist>' > ios/Runner/GoogleService-Info.plist
fi
echo "✅ Dummy secrets added"

Now Claude Code has near-full project context without seeing real secrets. The output quality jumped dramatically. It understood the architecture, followed existing patterns, and generated code that actually compiled.

The sync workflow was fast — about 30 seconds per iteration:

Terminal window
git diff > /tmp/changes.patch
cd ../real-project
git apply /tmp/changes.patch
npm test # or: pytest, go test, ./gradlew test, swift test
# if tests fail, iterate

The downside is maintenance. Every time a new secret is added to the real project, I need to update remove-secrets.sh. Every time a new required configuration file is added, I need to update add-dummy-secrets.sh. It’s friction, but manageable.

Verdict:

SecuritySpeedOutput QualityDaily Sustainability
⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

Best for: Medium-to-high security environments where you can invest 15-20 minutes in initial setup and accept occasional script updates.


Strategy 3: .claudeignore on Real Project

How it works:

Real Project/
├── .claudeignore # blocks sensitive files
├── secrets/ # ❌ blocked
│ └── *.pem
├── .env # ❌ blocked
├── docker-compose.yml # ❌ blocked
└── src/ # ✅ accessible

Work directly on your real project. Use a .claudeignore file to block Claude Code from accessing sensitive files and directories.

The experience:

This is the fastest strategy by far. Zero overhead. No duplicate projects. No sync scripts. No context loss. Claude Code sees your full architecture and produces excellent output.

Here’s a comprehensive .claudeignore that works for any stack:

# ============================================
# Universal Secrets
# ============================================
.env*
*.pem
*.key
*.p12
*.keystore
*.jks
credentials.json
service-account.json
**/secrets/**
# ============================================
# Infrastructure
# ============================================
docker-compose.prod.yml
docker-compose.override.yml
**/vault/**
secrets.yaml
vault-config.hcl
# ============================================
# Platform-Specific Secrets
# ============================================
# Android
google-services.json
local.properties
keystore.properties
# iOS
GoogleService-Info.plist
*.mobileprovision
*.certSigningRequest
# Firebase
firebase.json
.firebaserc
# Cloud Providers
.aws/
.gcp/
.azure/
# Node.js
.npmrc
# Python
.pypirc
# ============================================
# Proprietary Business Logic
# ============================================
src/**/trading/
src/**/pricing-engine/
src/**/encryption/impl/
lib/**/proprietary/
**/internal/algorithms/
# ============================================
# Build Artifacts (not secret but noisy)
# ============================================
node_modules/
dist/
build/
.next/
out/
__pycache__/
*.pyc
target/
bin/
obj/
.gradle/
.dart_tool/
Pods/
Podfile.lock
vendor/
.terraform/

The output quality is the best of all strategies because Claude Code has complete architectural context. It follows your patterns perfectly, understands your dependency injection setup, and generates code that compiles on the first try.

The trust question:

Here’s the honest part: .claudeignore is a software boundary, not a physical one. The files are still on your disk. You’re trusting that:

  1. Claude Code respects .claudeignore and doesn’t read blocked files
  2. There are no bugs that accidentally bypass the filter
  3. No files that reference secrets (like a README with example API calls) slip through

I’ve used this strategy for months on personal projects and client work where I control the risk policy. I’ve never seen Claude Code access a blocked file. But I can’t verify it in real-time. There’s no audit log showing “Claude Code requested X, was denied.”

Some companies can’t accept this. If your security policy requires provable guarantees that secrets were never accessible, this isn’t enough.

Edge cases:

Files that reference secrets but don’t contain them directly:

# docs/API.md - not a secret file, but contains secret references
## Authentication
curl -H "Authorization: Bearer $STRIPE_KEY" \
https://api.stripe.com/v1/customers

You need to either add docs/*.md to .claudeignore (loses valuable documentation context) or manually review every file that might reference secrets.

Which secrets matter for which platform:

Secret FilePlatforms
.env, .env.*All (universal)
docker-compose.prod.ymlBackend, fullstack
google-services.jsonAndroid
GoogleService-Info.plistiOS
credentials.jsonGCP (any)
*.keystore, *.jksAndroid, Java
*.p12, *.pemiOS, SSL certificates
local.propertiesAndroid
secrets.yamlKubernetes
.npmrcNode.js private registries
.pypircPython private packages
firebase.jsonFirebase (all platforms)

Verdict:

SecuritySpeedOutput QualityDaily Sustainability
⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

Best for: Solo developers, startups, and teams with moderate security requirements who trust software boundaries and can accept that blocked files are still on disk.


Strategy 4: Git Worktree + Clean Branch

How it works:

real-project/ # main worktree on 'master' (has secrets)
real-project-clean/ # second worktree on 'clean' branch (no secrets)
# Claude Code works here ↑
# Sync via git merge or cherry-pick

Use git worktree to create a second working directory of the same repository on a separate branch. The clean branch has all secrets removed and added to .gitignore. Claude Code works in the clean worktree. You sync changes between branches using git merge or git cherry-pick.

The experience:

This is the most elegant solution I’ve found. It combines the physical security of Strategy 2 (secrets literally don’t exist in the Claude Code workspace) with git-native sync (no manual patches).

Setup (one-time, ~10 minutes):

Terminal window
cd ~/projects/my-app
# Create clean branch
git checkout -b clean
# Remove all secrets
rm -f .env .env.local .env.production
rm -f docker-compose.prod.yml
rm -f **/credentials.json **/service-account.json
find . -name "*.pem" -delete
find . -name "*.key" -delete
find . -name "*.p12" -delete
find . -name "google-services.json" -delete
find . -name "GoogleService-Info.plist" -delete
# Add dummy secrets
echo "DATABASE_URL=postgres://user:pass@localhost:5432/mydb" > .env
echo "STRIPE_KEY=sk_test_DUMMY" >> .env
echo "JWT_SECRET=dummy-secret-for-dev" >> .env
git add -A
git commit -m "Remove secrets for AI workspace"
# Create second worktree
git worktree add ../my-app-clean clean
# Add real secrets to .gitignore on clean branch (prevents accidents)
cat >> .gitignore << EOF
# Real secrets (never commit on clean branch)
.env.production
docker-compose.prod.yml
**/credentials.json
**/service-account.json
*.pem
*.key
*.p12
google-services.json
GoogleService-Info.plist
EOF
git add .gitignore
git commit -m "Ignore secrets on clean branch"

Daily workflow:

Terminal window
cd ~/projects/my-app-clean # Claude Code workspace
# work with Claude Code
git add -A
git commit -m "Add retry logic to payment service"
cd ~/projects/my-app # real project
git cherry-pick clean # pull changes from clean branch
npm test # or: pytest, go test, cargo test, swift test
# verify with real secrets

The beauty: Claude Code’s changes are committed to the clean branch. You review them, test them in the real workspace, and cherry-pick them over. If something breaks, you haven’t polluted your main branch.

Why this works:

  • Physical security: The clean worktree doesn’t have secret files on disk. Even if Claude Code tries to read them, they don’t exist.
  • Full context: The clean branch has your full architecture, dependencies, and patterns — just with dummy secrets.
  • Git-native sync: No manual patches. git cherry-pick or git merge handles the sync in seconds.
  • Verifiable: You can ls the clean workspace and confirm secret files aren’t there.

The learning curve:

Most developers don’t know about git worktree. It’s intimidating the first time. But once you’ve set it up, it’s incredibly smooth. I now use this for every production project.

Verdict:

SecuritySpeedOutput QualityDaily Sustainability
⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

Best for: Teams with strong security policies who need provable guarantees, developers comfortable with git, and anyone working on long-term projects where setup time amortizes to zero.


Which Strategy Should You Use?

Choose based on your risk profile:

If you need…Use Strategy
Absolute maximum security, don’t care about speed#1 Demo Project
Strong security + good context, can handle setup#2 Duplicate + Patch
Maximum speed + great output, trust software boundaries#3 .claudeignore
Physical security + git workflow, comfortable with git#4 Git Worktree

My personal journey:

Started with #1. Lasted a week. Switched to #2 for two months. Now I use #4 for production work and #3 for personal projects.


Bonus: The Single Biggest Quality Improvement

Regardless of which strategy you use, add a CLAUDE_CONTEXT.md file to your project root. This is the single biggest improvement to output quality I’ve found.

Examples for different stacks:

Next.js/React

CLAUDE_CONTEXT.md
## Project: E-commerce Dashboard
**Tech Stack:**
- Next.js 14 + TypeScript + App Router
- React Server Components + Server Actions
- Prisma + PostgreSQL
- TailwindCSS + shadcn/ui
**Architecture:**

app/ ├── (auth)/ # Auth routes ├── api/ # API routes └── dashboard/ # Protected routes lib/ ├── actions/ # Server Actions ├── db/ # Prisma client └── utils/ # Shared utilities

**Critical Conventions:**
- Server Components by default, use "use client" only when needed
- All database access via Prisma, never raw SQL
- Server Actions for mutations, not API routes
- All secrets via process.env, never hardcoded
**Secret Files (for reference, never access):**
- `.env.local` - DATABASE_URL, STRIPE_SECRET_KEY
- `docker-compose.yml` - Database passwords
**When working on payment features:**
Reference `app/api/stripe/webhook/route.ts` as the canonical pattern.

Python/FastAPI

CLAUDE_CONTEXT.md
## Project: Analytics API
**Tech Stack:**
- FastAPI + Python 3.11
- SQLAlchemy + Alembic
- PostgreSQL + Redis
- Pydantic for validation
**Architecture:**

app/ ├── api/ # Routers ├── core/ # Config, dependencies ├── models/ # SQLAlchemy models ├── schemas/ # Pydantic schemas ├── services/ # Business logic └── repositories/ # Data access

**Critical Conventions:**
- Router → Service → Repository pattern (never skip layers)
- All config via Settings (Pydantic BaseSettings), never direct env access
- Async everywhere (async def, await)
- Type hints mandatory (mypy strict mode)
**Secret Files:**
- `.env` - DATABASE_URL, REDIS_URL, SECRET_KEY
- `docker-compose.yml` - Service passwords
**When working on authentication:**
Reference `app/services/auth_service.py` for token patterns.

Mobile (Kotlin Multiplatform)

CLAUDE_CONTEXT.md
## Project: Mobile Banking App (KMP)
**Tech Stack:**
- Kotlin Multiplatform (Android + iOS)
- Compose Multiplatform for UI
- Ktor for networking
- SQLDelight for database
- Koin for DI
**Architecture:**

shared/ ├── commonMain/ # Shared logic ├── androidMain/ # Android-specific └── iosMain/ # iOS-specific androidApp/ # Android UI iosApp/ # iOS UI

**Critical Conventions:**
- Business logic in commonMain, platform-specific only when necessary
- Sealed classes for all state (Loading, Success, Error)
- expect/actual for platform APIs
- All secrets injected via platform modules
**Secret Files:**
- Android: `local.properties`, `google-services.json`
- iOS: `GoogleService-Info.plist`, `Config.xcconfig`
**When working on network layer:**
Reference `shared/src/commonMain/.../ApiClient.kt` for patterns.

With this file in place, Claude Code’s output quality jumps 3-5x. It knows your patterns, follows your architecture, and doesn’t make assumptions about where to put code.

I wrote a deep-dive on this: Why CLAUDE.md Is the Most Important File in Your Project.


The Bottom Line

You don’t have to choose between Claude Code’s productivity and your project’s security. Pick the strategy that matches your risk tolerance:

  • Strategy #1: Maximum security, terrible speed (demo project)
  • Strategy #2: Strong security, good balance (duplicate + patch)
  • Strategy #3: Maximum speed, trust required (.claudeignore)
  • Strategy #4: Physical security + git workflow (worktree)

Whatever you choose, add CLAUDE_CONTEXT.md. That alone will 3x your output quality.

I started with copy-paste hell, moved to duplicate projects, and now run git worktrees for production and .claudeignore for personal work. The right strategy depends on your project, your team, and your security requirements.


Want to go deeper? The Claude Code Mastery course covers all of this and more — including the complete security module (Phase 2) with hands-on exercises. Phases 1-3 are free.

Get the free Claude Code Cheat Sheet — 50+ commands in a single PDF — when you join the newsletter.