The venv for Claude Code: Reproducible Team Environments
Your teammate cloned the repo, ran Claude Code, and the agent did something yours never would. The tool versions differ by two majors and the settings.json was never committed. Here's the fix.
A reproducible Claude Code team environment requires exactly three versioned artifacts: a committed CLAUDE.md project spec, a .claude/settings.json permissions config, and a devcontainer that pins your tool stack. Without all three in version control, every developer on your team is running a different agent against the same codebase — different allowed tools, different instruction sets, different language runtime versions. The fix is straightforward and borrows from a pattern Python developers learned a decade ago.
TL;DR: The Claude Code equivalent of venv activate is a committed CLAUDE.md + .claude/settings.json + a .devcontainer/devcontainer.json that pins your tool stack. Version-control all three, provision the environment with a devcontainer or Daytona, and every team member gets identical agent behavior. This eliminates the "works on my machine" class of debugging that emerges when AI agent configs drift across developers.
Why Claude Code Breaks on Team Handoff
When Claude Code runs against a codebase, its behavior is determined by four factors:
- The
CLAUDE.mdinstructions — what the agent is told to do, avoid, and prioritize - The
.claude/settings.jsonpermissions — which tools fire without an approval gate - The compute environment — Node version, Python version, installed CLIs, file system layout
- The model selection — which defaults to whatever the individual developer last set
Most teams commit the first item and forget the other three. The result: a developer clones the repo, runs Claude Code, and the agent behaves differently because they're on Node 18 when your CLAUDE.md assumes Node 22, or because their user-level .claude/settings.json has a broad allow: ["Bash(*)"] override that bypasses the project's deny list.
This gap is actively felt in the community. A thread in r/ClaudeCode recently surfaced a developer building a venv-like tool for Claude Code specifically to distribute consistent environments to coworkers — the demand is real and no official tooling addresses it yet. A parallel thread in r/claude showed developers trying to run multiple isolated Claude Desktop instances on macOS — not for parallel execution, but because they want per-project isolation and there's no native mechanism for it.
Goal
By the end of this tutorial, you'll have:
- A versioned agent environment definition checked into git
- A devcontainer spec that provisions the correct tool stack on any machine
- An onboarding path any developer on your team can complete in under two minutes
- Consistent, reproducible Claude Code behavior across your entire team
Prerequisites
- Claude Code installed:
npm install -g @anthropic-ai/claude-code - Docker (for the devcontainer-based approach)
- Git initialized in your project
ANTHROPIC_API_KEYset in your shell environment (each developer supplies their own key — never committed)- Recommended: Daytona for cloud-provisioned isolation (covered in the Grass section below)
Step 1: Commit CLAUDE.md as a First-Class Artifact
CLAUDE.md is the primary instruction set for Claude Code. An uncommitted CLAUDE.md means every developer's agent runs on different rules — or no rules at all.
A team-grade CLAUDE.md should specify the environment constraints the agent can rely on, the operations it's permitted to perform, and the conventions it must follow:
# Project Agent Config
## Environment assumptions
- Node 22.x is available
- Python 3.11+ is available
- `npm install` has been run before any agent task
- Do NOT modify files under `/generated` without explicit instruction
## Permitted operations
- Read any file in the repository
- Write to `src/`, `tests/`, `docs/`
- Run: `npm test`, `npm run lint`, `npm run build`
- Inspect git state: `git diff`, `git log`, `git status`
## Restricted operations
- Do NOT run database migrations without user confirmation
- Do NOT push to any branch directly
- Do NOT install global npm packages
- Do NOT write to `.env` files
## Code conventions
- TypeScript strict mode throughout
- All new functions require tests
- Conventional Commits format for any commit messages suggested
Commit this to the repository root. Every agent session on every developer's machine now starts from the same instruction baseline.
One useful pattern: maintain a CLAUDE.team.md alongside CLAUDE.md. The root file stays project-scoped; the team file documents workflow norms — which model to use by default, when to run in plan mode versus build mode, what to do when the agent hits an ambiguous task. For how to structure a multi-file config hierarchy without it becoming a maintenance burden, see Managing Claude Code Config Sprawl.
Step 2: Version Control .claude/settings.json
Claude Code's permission layer lives in .claude/settings.json. This file controls which tool invocations fire automatically, which trigger an approval gate, and which are blocked outright. If it's not committed, every developer's agent has a different blast radius.
A safe team baseline:
{
"model": "claude-sonnet-4-6",
"permissions": {
"allow": [
"Bash(npm test)",
"Bash(npm run lint)",
"Bash(npm run build)",
"Bash(git diff*)",
"Bash(git log*)",
"Bash(git status)",
"Read(*)",
"Write(src/**)",
"Write(tests/**)",
"Write(docs/**)"
],
"deny": [
"Bash(git push*)",
"Bash(git reset --hard*)",
"Bash(rm -rf*)",
"Bash(sudo*)",
"Write(.env*)",
"Write(**/.env*)"
]
}
}
This config gives the agent broad read access, scoped write access, and standard build commands — while blocking destructive operations and environment file writes explicitly.
What not to commit: .claude/settings.local.json is per-developer overrides and should be in .gitignore. API keys and model billing credentials should never appear in either file.
Note that user-level settings at ~/.claude/settings.json can override project-level settings. A developer with a permissive user-level config can expand their agent's permissions beyond what the project config allows. For the full treatment of the permission layer — including PreToolUse hooks as a second line of defense — see How to Build Human-in-the-Loop Approval Gates for AI Coding Agents.
Step 3: Define Your Devcontainer
This is the step most teams skip, and the one that causes the most debugging. A devcontainer (defined in .devcontainer/devcontainer.json) pins the compute environment: OS, language runtimes, CLI tools — and Claude Code itself at a specific version.
{
"name": "claude-code-env",
"image": "mcr.microsoft.com/devcontainers/javascript-node:22",
"features": {
"ghcr.io/devcontainers/features/python:1": {
"version": "3.11"
},
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"postCreateCommand": "npm install -g @anthropic-ai/claude-code@latest && npm install",
"remoteEnv": {
"ANTHROPIC_API_KEY": "${localEnv:ANTHROPIC_API_KEY}"
},
"customizations": {
"vscode": {
"extensions": ["esbenp.prettier-vscode"]
}
}
}
Key decisions:
- Base image pins Node 22 — eliminates the version mismatch class of failures entirely
- Python feature adds 3.11 alongside Node for mixed-stack projects
postCreateCommandinstalls Claude Code on every fresh environment — each container starts with the agent availableANTHROPIC_API_KEYis injected from the local shell environment at container creation time; the key never lives in the spec itself (each developer brings their own key)
To pin Claude Code to a specific version rather than @latest, replace with @anthropic-ai/claude-code@1.x.x. This is worth doing on teams where agent behavior stability is more important than access to new features.
Step 4: Add a Bootstrap Script for Non-Container Setups
Not every developer uses VS Code or Dev Containers. A bootstrap script covers the manual path:
#!/usr/bin/env bash
# scripts/setup-agent-env.sh
set -euo pipefail
echo "==> Checking Node version..."
node --version | grep -E "^v(20|22)" || {
echo "ERROR: Node 20 or 22 required. Current: $(node --version)"
exit 1
}
echo "==> Installing Claude Code..."
command -v claude >/dev/null 2>&1 || npm install -g @anthropic-ai/claude-code
echo "==> Installing project dependencies..."
npm install
echo "==> Verifying Claude Code..."
claude --version
echo ""
echo "Agent environment ready."
echo " CLAUDE.md: loaded automatically from repo root"
echo " .claude/settings.json: team permissions applied"
echo " Model: claude-sonnet-4-6 (set in settings.json)"
echo ""
echo "Run: claude"
Make it executable and commit everything together:
chmod +x scripts/setup-agent-env.sh
git add CLAUDE.md .claude/settings.json .devcontainer/ scripts/setup-agent-env.sh
git commit -m "chore: add reproducible Claude Code team environment"
How to Verify the Environment Is Consistent
After onboarding a teammate, run this checklist on both machines and compare outputs:
# 1. Confirm Claude Code version
claude --version
# 2. Confirm CLAUDE.md is loaded
claude --print "Summarize your CLAUDE.md in one sentence."
# Should reflect your project-specific instructions
# 3. Confirm Node version
node --version
# Should be v22.x.x
# 4. Check for user-level permission overrides
cat ~/.claude/settings.json 2>/dev/null | python3 -m json.tool
# Should not contain project-specific tool overrides
If two developers run these checks and get the same outputs, their environments are consistent. The most common divergence point is (4) — a developer with a permissive user-level config from a previous project that's bleeding into the current one.
Troubleshooting
Agent ignores the project CLAUDE.md
Claude Code loads CLAUDE.md from the current working directory upward. If a developer runs claude from a subdirectory, the root CLAUDE.md may not be picked up. Always run claude from the repository root, or add a CLAUDE.md in relevant subdirectories that imports the project baseline.
settings.json permissions not applied as expected
Claude Code merges settings in this order: user-level (~/.claude/settings.json) → user-local (~/.claude/settings.local.json) → project-level (.claude/settings.json). User-level allow entries expand permissions beyond the project config. Audit with cat ~/.claude/settings.json on the affected developer's machine.
Environment mismatch persists inside the devcontainer
Check that postCreateCommand ran successfully. Add a completion marker to confirm:
"postCreateCommand": "npm install -g @anthropic-ai/claude-code@latest && npm install && echo 'SETUP_COMPLETE' > /tmp/.devcontainer-ready"
Then verify: cat /tmp/.devcontainer-ready should print SETUP_COMPLETE.
MCP servers not available in the container
The devcontainer spec doesn't automatically provision MCP server configurations. If your team uses MCP integrations, those configs need to be versioned and added to postCreateCommand. See The MCP Server Ecosystem in 2026 for how to declare MCP configs as first-class artifacts.
Developers using different models despite model in settings.json
User-level settings can override the project model. Document the intended model in CLAUDE.md explicitly ("Use claude-sonnet-4-6 for all tasks in this repo") as a fallback enforcement layer.
How Grass Makes This Workflow Better
The steps above give your team a consistent local environment. Grass takes the same config and runs it on an always-on cloud VM — so the environment isn't just reproducible, it's persistent and accessible from anywhere.
The local devcontainer limitation: Your laptop sleeps. Your Docker container exits. A developer finishing a long-running agent task at the end of the day can't hand it off to the next developer in the morning without manual session recovery, re-establishing context, and hoping the partial output was saved somewhere.
The cloud VM approach: Daytona reads .devcontainer/devcontainer.json natively and provisions isolated sandboxes from it — 90ms startup, full kernel/filesystem/network isolation per workspace. The same devcontainer spec you committed in Step 3 becomes the provisioning definition for each team member's cloud environment.
# Create a Daytona workspace from your repo — reads .devcontainer/ automatically
daytona create https://github.com/yourorg/yourrepo
Daytona runs postCreateCommand, installs Claude Code, and your agent environment is live in the cloud. CLAUDE.md is loaded. settings.json is applied. Tool stack is pinned. And crucially: the session continues running when the developer closes their laptop.
Grass layers on top of Daytona to give each developer mobile access to their running agent:
# On the Daytona VM, after daytona create
npm install -g @grass-ai/ide
grass start --network tailscale
Scan the QR code from the Grass mobile app. You now have real-time access to the agent session — streaming output, diff viewer, approval gates — from your phone. The session persists on the cloud VM independent of any device's uptime.
For teams running parallel agents across feature branches, each developer gets their own isolated Daytona workspace from the same devcontainer spec. No shared filesystem, no conflicting agent sessions, no "which Claude wrote this?" ambiguity. The Coordinate Multiple Claude Code Sessions on a Shared Repo architecture applies directly to this setup.
For a step-by-step walkthrough of the full Grass + Daytona connection — workspace creation, Claude Code installation, Tailscale setup, and session persistence — see How to Set Up Claude Code on Daytona.
Grass is the operational upgrade to this workflow, not a prerequisite. The core tutorial above works end-to-end without it.
FAQ
What is the venv equivalent for Claude Code?
The Claude Code equivalent of a Python virtual environment is a combination of three versioned artifacts: CLAUDE.md (agent instruction spec), .claude/settings.json (permissions and model config), and .devcontainer/devcontainer.json (tool stack and runtime pins). All three must be committed to version control. Together they define a reproducible environment that produces consistent agent behavior regardless of which developer runs it or on which machine.
How do I share Claude Code configuration with my team?
Commit CLAUDE.md and .claude/settings.json to your git repository root. Do not commit .claude/settings.local.json (per-developer overrides) or any file containing ANTHROPIC_API_KEY. Each developer sources their own API key via an environment variable (export ANTHROPIC_API_KEY=...). The project-level config in version control defines the shared baseline; individual developers can override locally without affecting teammates.
Why does Claude Code behave differently on different developers' machines?
Three common causes in order of frequency: (1) user-level ~/.claude/settings.json overrides that expand or restrict the project permission set; (2) different tool versions — the agent invokes tools that are missing or at different versions on different machines; (3) diverged or missing CLAUDE.md files, especially when the file is not committed and developers have modified their local copies independently. Audit each in order — the verification checklist above surfaces all three.
Can Daytona provision Claude Code environments for my whole team from a devcontainer spec?
Yes. Daytona reads .devcontainer/devcontainer.json natively and creates isolated sandboxes from it. The Daytona documentation covers workspace creation via the SDK, CLI, and API. Each workspace gets its own filesystem, network, and process namespace — the same spec produces identical, isolated environments for every developer on the team.
How do I prevent a developer's local ~/.claude/settings.json from overriding the project config?
You cannot fully prevent user-level overrides — Claude Code merges configs with user settings taking precedence. The practical mitigations are: document the expected baseline permissions in CLAUDE.md as a normative spec; include the verification checklist in your onboarding runbook; and use Daytona or devcontainer environments for critical agent work, since fresh containers have no ~/.claude/settings.json and start from the project config only.
Next Steps
- Commit the three files —
CLAUDE.md,.claude/settings.json,.devcontainer/devcontainer.json— and push to your team repo today - Run the verification checklist with one other developer to confirm consistent outputs before rolling out to the full team
- Evaluate the cloud VM layer — Grass offers a free tier (10 hours, no credit card required) to test the always-on, mobile-accessible approach built on Daytona
Published by Grass — a machine built for AI coding agents. Always-on cloud VM, agent-agnostic, accessible from your laptop, phone, or automation. Works with Claude Code, Codex, and OpenCode.