⌨️

Claude Code in Practice

Setup, workflows, plugins, security & lessons learned

Internal Knowledge Sharing · ~50 min · Gemma Analytics

What we'll cover

📄
CLAUDE.md & Permissions
The foundation — project context, permission model, security defaults
~10 min
🧠
Modes & Prompting Patterns
Plan mode, the interview pattern, context-window discipline
~10 min
🔌
Plugins & Skills
From instructions to capabilities — what we use and what to build
~10–15 min
🚀
Production Workflows & Lessons Learned
Worktrees, the dbt refactor case study, what broke, security architecture
~15 min

🎯 Live showcases throughout · Open discussion at the end

CLAUDE.md: The Foundation

The CLAUDE.md file is Claude's project context — it tells Claude what the project is, how to work in it, and where to look for more detail.

Philosophy: keep it short. Use pointers.

  • Project purpose & architecture overview
  • Dev commands (build, test, lint)
  • Key conventions & constraints
  • Pointers to deeper docs — not duplication

⚠️ A giant CLAUDE.md becomes counterproductive — Claude spends tokens processing instructions instead of doing work. Point to docs; don't paste them in.

Instruction precedence:

~/.claude/CLAUDE.md Personal (global) │ ▼ ./CLAUDE.md Project root │ ▼ ./subdir/CLAUDE.md Subdirectory (additive) │ ▼ Project documentation README, ARCHITECTURE.md, etc. │ ▼ Actual code / data / tooling What Claude reads at runtime

Project-level overrides personal. Subdirectory CLAUDE.md adds context when Claude works in that path.

Our CLAUDE.md

🖥️ Showcase
## What is Tundri

Tundri is a Python CLI tool that manages
Snowflake database objects and permissions
declaratively.

## Development Commands

uv sync
uv run pytest -v
uv run ruff check .
uv run ruff format .

## Architecture

cli.py   → Argparse CLI entry point
core.py  → Main logic: inspect, compare, DDL
parser.py→ Parse Permifrost YAML specs
objects.py→ Frozen dataclasses for SF objects

## Conventions

- Use uv for all Python operations
- Type hints throughout
- Frozen dataclasses with custom equality
- Never commit directly to main

What's included — and what's not:

✓ Included

  • One-sentence purpose
  • Dev commands (copy-paste ready)
  • Module map (what's where)
  • Key conventions
  • Version constraints

✗ Deliberately left out

  • Full API documentation
  • Usage examples & tutorials
  • Deployment procedures
  • Detailed architecture deep-dives
  • Changelog / release history

These live in dedicated docs. CLAUDE.md points to them.

Permissions: Don't Give Claude the Keys

🖥️ Showcase

Security first, convenience second.

What Claude can do (by default) ├── Read any file in the project ├── Run allowed shell commands ├── Edit files └── Search / navigate code What requires approval ├── ? Run unknown shell commands ├── ? Install packages └── ? Access external services What is blocked ├── Database queries (on API) ├── Reading .env / secrets ├── Production data access └── Push to main

Permission mode: --dangerously-skip-permissions only in our controlled Slack bot environment with system-prompt-level restrictions.

The critical separation:

Code access ≠ Data access

Our approach:

Anthropic API — code work only
Read/write source code, run tests, create PRs

AWS Bedrock — code + data access
All traffic stays within our AWS account.
Database queries, dbt show, client data.

Switch with provider bedrock in our Slack bot, or claude-db in the terminal.

Plan Before You Build

🖥️ Showcase

Plan Mode = Claude explores and thinks without modifying anything.

Question / task │ ▼ Explore read code, grep, understand │ ▼ Plan propose approach, trade-offs │ ▼ Review / align human checks the plan │ ▼ Implement Claude writes code │ ▼ Validate tests, review, verify

Why this matters:

  • Claude reads the codebase before writing
  • Surfaces dependencies you forgot about
  • You catch wrong assumptions before they become code
  • The plan becomes documentation

Without planning:
Claude implements the first approach that seems plausible.
You spend 20 minutes debugging a wrong assumption.

With planning:
Claude explores 3 approaches, explains trade-offs.
You pick the right one. Implementation is clean.

Commands: Shift+Tab toggles Plan Mode · /plan in prompt

Stop Guessing: "Interview Me"

🖥️ Showcase

❌ Ambiguous prompt

"Add logging to the pipeline"

Claude guesses: which pipeline? what level? what format? where to log? Produces something that works but isn't what you wanted.

✓ Interview prompt

"I need to add logging to the pipeline.
Before you implement anything, interview
me about the requirements. Ask me about
scope, format, destination, and any
constraints I haven't mentioned."

Claude asks 4–6 targeted questions. You clarify. Then it builds exactly what you need.

Why this works

  • Exposes hidden assumptions — yours and Claude's
  • Clarifies scope — "all pipelines" vs "just the failing one"
  • Surfaces constraints — "we already use structlog" etc.
  • Reduces rework — get it right the first time

Pattern variations:

  • "Before implementing, ask me 5 questions about what I actually need"
  • "Interview me about the requirements — don't assume anything"
  • "What questions do you have before starting?"

Works especially well for: new features, refactoring scope, data model changes, API design decisions.

Context Is a Resource

Context window ≈ 200k tokens Fresh session ┌─────────────────────────────────────┐ │ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ Clean └─────────────────────────────────────┘ After heavy exploration ┌─────────────────────────────────────┐ │ ████████████████████████░░░░░░░░░░░ │ Filling up └─────────────────────────────────────┘ Compaction kicks in ┌─────────────────────────────────────┐ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ Summarized └─────────────────────────────────────┘ ↑ Earlier context is compressed Details may be lost

Signs a session is going off track:

  • Claude re-reads files it already explored
  • Suggestions contradict earlier decisions
  • Responses become generic / less specific
  • Claude "forgets" constraints you stated earlier

Context discipline

Continue when:

  • Working on related changes in the same area
  • Iterating on a specific implementation
  • Claude still references earlier context accurately

Start fresh when:

  • Switching to a different task / area of code
  • Context feels degraded
  • You've been in the same session for 20+ exchanges
  • The task is complete and you're starting something new

Useful commands:

/cost  — check token usage & cost
/compact — manually trigger compaction
/clear  — reset context in current session
Ctrl+C × 4 — exit and start fresh

💡 Sometimes the best prompt is a fresh session.

From Instructions to Capabilities

Mechanism What it does When to use
CLAUDE.md Persistent project instructions — always loaded Project context, conventions, constraints
Prompt Task-specific direction — one-shot Specific task, question, or request
Skill Reusable workflow with scripts & templates Repeatable multi-step process
Plugin Packaged set of skills, hooks, agents & references Domain-specific capability bundle
Hook Shell command triggered by Claude events Validation, guards, automation
Agent Sub-agent with scoped tools & system prompt Parallelism, specialised reviewer, focused exploration

Escalation path:   If you're repeating a prompt → make it a CLAUDE.md instruction   If it has steps → make it a skill   If it spans a domain → make it a plugin

What We Actually Use

🖥️ Showcase

Anthropic built-in

  • /code-review — diff-based review
  • /simplify — cleanup & refactor
  • /init — bootstrap CLAUDE.md
  • /security-review — security audit
  • /deep-research — multi-source research

Third-party

  • superpowers — plans, sub-agent dev
  • dbt-agent-skills — dbt workflow

Gemma custom

  • gemma-dbt — base models, source files, repo validation
  • gemma-dlt — connector creation & debugging
  • gemma-snowflake — tundri setup, keypair management
  • gemma-airflow — DAG creation, CI/CD setup
  • gemma-kimball — dimensional modelling
  • gemma-deployment-security — infra audits
  • gemma-prs — PR review & implementation
  • gemma-commercial — proposals, contracts

All 20+ custom plugins live in gemma-agentic-toolkit — a monorepo with independent semver versioning per plugin. Auto-loaded into every Gemmbot session via --plugin-dir. Skills invoked with /plugin-name:skill-name.

What Should We Build Next?

"What repetitive engineering task do you wish
Claude could handle better?"

The decision tree:

Repetitive task identified │ ┌──────┴──────┐ │ │ Simple? Complex? │ │ ▼ ▼ Prompt or Skill or CLAUDE.md Plugin │ │ │ ┌────┴────┐ │ │ │ │ Reusable Unique │ across to one │ projects project │ │ │ │ ▼ ▼ │ Plugin Project │ skill ▼ Done ✓

Where our plugins came from:

  • gemma-dbt — "I keep writing the same base model boilerplate"
  • gemma-dlt — "Setting up a new connector takes 2 hours of scaffolding"
  • gemma-snowflake — "Keypair rotation is error-prone and manual"
  • gemma-deployment-security — "We need consistent security audits"
  • gemma-kimball — "Dimensional modelling follows patterns, let's encode them"

Every plugin started as a repeated prompt,
graduated to a CLAUDE.md note,
and eventually became a skill.

Power-User Workflow

Git repository │ ┌──────────────┼──────────────┐ ▼ ▼ ▼ Worktree A Worktree B Worktree C │ │ │ Claude #1 Claude #2 Claude #3 Feature Refactor Review │ │ │ ▼ ▼ ▼ PR #42 PR #43 PR #44 │ │ │ └──────────────┴──────────────┘ │ merge to main

Git worktrees = multiple working directories from the same repo. Each gets its own Claude session with isolated context.

Setup:

git worktree add ../repo-feat feat-branch
git worktree add ../repo-refactor refactor
git worktree add ../repo-review review

# Each in its own terminal tab:
cd ../repo-feat && claude
cd ../repo-refactor && claude
cd ../repo-review && claude

Why worktrees over branches:

  • No context switching — each dir is independent
  • Claude in session A doesn't see session B's changes
  • Parallel work with zero conflicts until merge
  • Clean context per task = better output

Case Study: The dbt Refactor

Problem

Large dbt project needs refactoring — model restructuring, naming conventions, documentation, tests. Too big for one session.

Approach

Break into independently verifiable model groups.
Each group: one Claude session → one PR → one validation cycle.

Large refactor │ ┌─────┼─────┬─────┐ ▼ ▼ ▼ ▼ A B C D Model groups │ │ │ │ test test test test dbt build per group │ │ │ │ PR PR PR PR Review each └─────┴─────┴─────┘ │ Integration test dbt build --full-refresh │ Data validation Compare row counts, sums

Division of work:

🤖 Claude

  • Read existing models
  • Apply naming conventions
  • Restructure SQL
  • Generate schema.yml
  • Add generic tests
  • Update refs

👤 Human

  • Define model groups
  • Set naming rules
  • Review business logic
  • Validate data output
  • Approve PRs
  • Run integration tests

Key prompt pattern:

"Refactor only the models in models/staging/
shopify/. Follow naming convention stg_shopify__
{entity}. Update schema.yml. Don't touch
anything outside this directory."

Result: Weeks of refactoring → days. Each group independently validated. No data regression.

What Worked / What Didn't

✓ Worked

  • Small, bounded tasks
    "Refactor this directory" not "refactor the project"
  • Planning before implementation
    Explore → plan → align → implement
  • Good project context (CLAUDE.md)
    Claude follows conventions without being told each time
  • Automated validation
    dbt build, pytest, ruff — let CI catch what review misses
  • Human review of business logic
    Claude gets syntax right; humans verify semantics
  • Interview pattern for requirements
    Reduces rework by 80%

✗ Didn't work

  • Overly broad prompts
    "Fix the data pipeline" — which pipeline? what's broken?
  • Monster sessions
    Context degradation → contradictory suggestions
  • Skipping validation
    "Claude wrote tests, so it must be correct" — no.
  • "Refactor everything at once"
    Unreviable PRs, untraceable changes
  • Excessive permissions
    More access ≠ more useful. Often the opposite.
  • Trusting without verifying
    Claude confidently generates plausible-but-wrong SQL

Humans Still Own the Important Decisions

🤖 Claude

  • 🔍  Explore & navigate codebases
  • ⌨️  Implement changes
  • ♻️  Refactor & restructure
  • 🧪  Write & run tests
  • 📝  Explain & document
  • 🔧  Scaffold & boilerplate
  • 🐛  Debug & diagnose

👤 Human

  • 🏗️  Architecture decisions
  • 🔒  Security boundaries
  • 📋  Requirements & priorities
  • ⚖️  Trade-off evaluation
  • ✅  Validation & approval
  • 📊  Data correctness
  • 🎯  Accountability

Claude accelerates engineering work — it does not remove engineering responsibility.

Code Access ≠ Data Access

Developer │ ▼ Claude Code │ ├── Source code ├── Dev environment ├── dbt compile ├── dbt run / build (transforms in warehouse) ├── dbt show (returns data locally) ├── Test / fixture data ? (must be synthetic) ├── .env / secrets ├── Client data files ├── Database queries (on Anthropic API) └── Database queries (on AWS Bedrock)

Why two providers?

Anthropic API (default)
Data flows through Anthropic's servers.
→ Code work only. No client data.

AWS Bedrock
All traffic stays within Gemma's AWS account (eu-central-1).
→ Full access including databases and client data.

The rule is simple:

Does the task involve client data?
No → Anthropic API (faster, cheaper)
Yes → AWS Bedrock (data stays in our account)

# Terminal
claude-db          # One-off Bedrock session
bedrock-on         # Toggle for current shell

# Slack bot
provider bedrock   # Switch in current thread

Our Security Architecture

Slack │ ▼ ┌─────────────┐ │ Gemmbot │ Node.js bot wrapper │ (Slack bot) │ Session management └──────┬──────┘ │ ┌────────────┼────────────┐ ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ │ Anthropic API │ │ AWS Bedrock │ │ (default) │ │ (eu-central-1) │ │ │ │ │ │ Code access │ │ Code access │ │ Data access │ │ Data access │ │ Secrets │ │ DB queries │ └──────────────────┘ └──────────────────┘ │ │ ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ │ Claude Code │ │ Claude Code │ │ --print mode │ │ --print mode │ │ + system prompt │ │ + system prompt │ │ + plugins │ │ + plugins │ └────────┬─────────┘ └────────┬─────────┘ │ │ ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ │ Source code │ │ Source code │ │ Git repos │ │ Git repos │ │ Tests, CI/CD │ │ + Snowflake │ │ Documentation │ │ + Client data │ └──────────────────┘ └──────────────────┘

Additional boundaries:

  • Each session = isolated workspace directory
  • Secrets injected via env vars, never stored in prompts
  • GitHub Actions use Bedrock via OIDC (no long-lived keys)
  • Git: never push to main, always PR

GitHub Actions integration:

  • @claude mentions → agentic task via Bedrock
  • PR opened → automated code review (9-category sweep)
  • Scheduled audits → security reports via plugins
  • Bot identity: "Gemma Claude Assistant" GitHub App

Claude Code Playbook

  1. Keep CLAUDE.md short — point to docs, don't duplicate them
  2. Start with minimal permissions — expand only when needed
  3. Separate code access from data access
  4. Plan complex work before implementing
  5. Ask Claude to interview you when requirements are unclear
  1. Treat context as a limited resource — fresh sessions for new tasks
  2. Use skills for repeatable workflows — promote from prompts
  3. Break large work into independently verifiable tasks
  4. Validate with tests, CI, and human review — not just Claude
  5. Keep humans accountable for architecture, security, and data

These aren't Claude-specific principles — they're good engineering practices
applied to a new tool. The tool changes; the discipline doesn't.

Open Exchange

What are you going to try differently tomorrow?

💬

Share your
workflows

Ask
questions

🔌

Propose
new skills

Gemma Agentic Toolkit → github.com/Gemma-Analytics/gemma-agentic-toolkit
Claude Code docs → docs.anthropic.com/en/docs/claude-code