Claude Code on a React Native Codebase: Guardrails That Actually Change the Output A developer detailed how to improve Claude Code's output on React Native codebases by using guardrails, including a concise CLAUDE.md file and permission rules to prevent unwanted native project edits. The approach measurably reduces 'compiles but wrong' output by providing context and constraints. Every React Native developer has run the same experiment by now: point an AI agent at the codebase, ask for a feature, watch it produce something that compiles, and then quietly rewrite half of it before merging. The demo works. The diff does not. The gap is not model quality. It is that an agent with no constraints makes the statistically average choice at every fork, and the statistically average React Native choice is frequently wrong for your app: wrong navigation pattern, wrong state library, a fresh useState where you have a store, an inline style where you have a design system. Guardrails close that gap. Here are the ones that measurably changed what Claude Code hands me on RN projects. ios/ , android/ , and native config with permission rules AND an instruction. Belt and suspenders. Unguided agent output on an RN codebase fails in predictable ways: None of these are intelligence failures. They are context failures, and context is exactly the thing you control. Claude Code reads a CLAUDE.md file from your project root at the start of every session and treats it as standing instructions. Most people either skip it or write a 300-line novel the model has to dig through. The high-leverage version is short and opinionated: CLAUDE.md This is an Expo managed workflow app. TypeScript strict mode. Navigation: expo-router, file-based. State: zustand stores in src/stores/. Data fetching goes through src/api/client.ts only. Never call fetch directly. UI: use the components in src/components/ui/. Do not write inline styles; use the theme tokens in src/theme/. Never modify anything in ios/, android/, or app.json without asking first. Before finishing any task, run: npx tsc --noEmit && npm run lint && npm test. If any of those fail, fix them before presenting the diff. That is the whole file. Every line kills one category of "compiles but wrong" output. The first paragraph replaces the model's generic defaults with your actual stack. The second paragraph sets the boundaries and the exit criteria. The test for whether a line belongs in CLAUDE.md: have you corrected the agent for this twice? Then it goes in. Otherwise it is noise. On RN specifically, the highest-damage failure mode is the agent "helpfully" editing native projects: a Podfile tweak here, a gradle change there, an Info.plist permission it decided you needed. These changes are invisible in a JS-focused review and surface as build failures days later. I fence this at two layers. Layer 1: permission rules. Claude Code supports allow/deny permission rules in .claude/settings.json at the project root, so the constraint ships with the repo: { "permissions": { "deny": "Edit ios/ ", "Edit android/ ", "Write ios/ ", "Write android/ ", "Bash pod: ", "Bash npx expo prebuild: " } } Layer 2: the instruction. The "never modify ios/, android/, or app.json without asking" line in CLAUDE.md. This is not redundant. Permission rules are enforcement; the instruction changes planning . Without it, the agent plans a native change, hits the wall, and flails. With it, the agent routes around native work from the start or stops and asks, which is what you actually want. If you are on Expo managed workflow, you get this guardrail almost for free: keep the agent in JS, keep config in app.json under human control, and let EAS handle the native layer. The size of the task changes the quality of the output more than any prompt wording. My working rule: if you cannot describe the expected diff in one sentence, the task is too big. Bad: "Add a profile feature." Good, as a sequence: 1. Add a ProfileScreen at app/profile.tsx using the ui/ components. Static layout only, hardcoded data. No navigation changes yet. 2. Add a useProfile hook in src/hooks/ that loads the profile via src/api/client.ts. Include a loading and error state. 3. Wire useProfile into ProfileScreen. Handle loading/error with our existing