Claude Code Integration#
This project is a showcase for integrating Claude Code into a devcontainer-based development workflow. The pattern is language-agnostic — it works the same way for TypeScript (this project), Python (robot-arm-sim), and Go (dra-usbip-driver).
Design principle: autonomy through isolation#
Claude Code is most productive when given broad permissions — freedom to read, write, run tests, and push code without constant approval prompts. But broad permissions are only safe inside a controlled environment. The devcontainer provides that environment: a disposable, reproducible sandbox where Claude can operate freely without risk to the host machine or unrelated repositories.
This is the central trade-off:
More autonomy inside the container, strict enforcement that it runs nowhere else.
Configuration layers#
Claude Code configuration lives in three layers, each with a different scope and audience:
CLAUDE.md — project instructions#
Checked into the repo root. Loaded into every conversation, so it must be concise (aim for under 50 lines). Contains:
Build commands — how to lint, test, build, and run docs
Hard rules — things that cause real damage if forgotten (e.g. “never push to main”, “always run
just checkbefore committing”)Foot-guns — silent failures, sync requirements, non-obvious gotchas
Conventions — commit style, coding patterns (one line each)
Everything else belongs in skills (loaded on demand) or in documentation (explored when needed). See the trim-claude-md pattern below.
.claude/settings.json — permissions and hooks#
Controls what Claude Code is allowed to do. The key sections:
Permissions grant broad access inside the devcontainer:
{
"permissions": {
"allow": ["Read", "Edit", "Write", "Bash(*)", "WebSearch", "WebFetch(*)"],
"deny": [
"Bash(git push --force *)",
"Bash(git reset --hard*)",
"Bash(ssh *)", "Bash(ssh-agent *)", "Bash(*ssh-agent*)",
"Bash(scp *)", "Bash(rsync *)", "Bash(sftp *)"
]
}
}
allow — operations that proceed without asking. File operations and bash are safe inside the container.
deny — operations that are blocked outright. These are “escape vectors” — commands that reach outside the container (SSH, SCP) or are destructive (force push, hard reset). The
ssh-agentpatterns prevent Claude from re-enabling agent forwarding after the devcontainer has blankedSSH_AUTH_SOCK. We usedenyrather thanpromptbecause the prompt dialog has an “always allow” option that is too easy to accept accidentally, which would permanently bypass the protection.
Hooks enforce the devcontainer requirement:
{
"hooks": {
"UserPromptSubmit": [{
"hooks": [{
"type": "command",
"command": "if [ -z \"$REMOTE_CONTAINERS\" ]; then echo 'BLOCKED: ...'; exit 2; fi; if [ -n \"$SSH_AUTH_SOCK\" ]; then echo 'BLOCKED: ...'; exit 2; fi"
}]
}]
}
}
The hook runs two checks on every prompt:
$REMOTE_CONTAINERSmust be set — confirms we are inside a devcontainer. If absent, every prompt is blocked, preventing accidental use of the permissive settings on the host.$SSH_AUTH_SOCKmust be empty — confirms SSH agent forwarding is disabled. The devcontainer blanks this variable, but this check catches cases where it gets re-introduced (e.g. by a shell profile or a manually started agent).
.claude/skills/ — on-demand knowledge#
Skills are markdown files that Claude loads only when relevant (based on
the description field). They keep detailed knowledge out of CLAUDE.md
while making it available when needed.
Good candidates for skills:
Skill |
When it triggers |
|---|---|
|
Editing files under |
|
Investigating CI failures |
|
Python code that reads/writes YAML |
|
Saving work state to persistent memory |
Skills that are project-agnostic can be shared across repositories via the copier template. Project-specific information can be added to per project skills or go in the documentation.
Keeping CLAUDE.md lean#
CLAUDE.md is loaded into every conversation turn. A long file wastes
tokens, slows responses, and dilutes critical instructions. The rule of
thumb:
Keep inline: rules that cause damage if forgotten on any task
Move to skills: stable knowledge only needed sometimes (architecture, integration patterns, detailed conventions)
Drop entirely: anything discoverable by exploring the codebase (directory trees, file listings, config schemas)
Credential isolation#
The devcontainer isolates credentials from the host to limit the blast radius of prompt injection attacks (malicious instructions hidden in issues, web content, or repository files):
SSH agent forwarding disabled —
SSH_AUTH_SOCK=""prevents access to host SSH keysGit credential helper blanked on every container start — the
postStart.shscript removes credential helpers and SSH URL rewrites that VS Code copies from the host gitconfigScoped GitHub PAT — authentication uses a fine-grained token limited to specific repositories, stored in a per-repo container volume
Network escape vectors denied — SSH, SCP, RSYNC are blocked outright even inside the container
See Project Conventions for the full credential isolation setup, devcontainer details, and volume persistence strategy.
Cross-language conventions#
The Claude Code integration pattern sits on top of a set of
language-agnostic project conventions — just as the task runner,
Conventional Commits, Sphinx + Diataxis docs, pre-commit hooks, and a
consistent GitHub Actions CI structure. These are documented separately
in Project Conventions.
Tool compatibility: Claude Code and GitHub Copilot#
The configuration in this project works with both
Claude Code and
GitHub Copilot. Copilot reads
CLAUDE.md directly (alongside its own .github/copilot-instructions.md
and AGENTS.md formats), so there is no need to duplicate project
instructions. Skills in .claude/skills/ are also picked up automatically
by Copilot — the SKILL.md frontmatter format is an open standard shared
across Claude Code, Copilot, Cursor, Gemini CLI, and other tools.
The difference is in the safety layer. The .claude/settings.json
permissions, hooks, and deny rules are Claude Code features — Copilot does
not enforce them. For Copilot users, the devcontainer still provides
isolation, but the fine-grained permission and hook controls described
above are not applied.
Further reading#
Project Conventions — shared tooling, devcontainer setup, and conventions across TypeScript, Python, and Go projects
Building a Robot Simulator with Claude — detailed walkthrough of Claude Code on a Python project, including prompting principles and lessons learned
python-copier-template — the upstream template this pattern extends