Home › Study guides › CCDV-F › Domain 2 › Lesson 2.6
CCDV-F · Domain 2 · 33.1% of the exam · Lesson 2.6 · 20 min read
Configuration management: pinning and versioning a Claude system
Why a Claude app can change with no code change, and how to pin model IDs, version prompts, share CLAUDE.md and settings.json, and pin plugin dependencies.
Written against skill 2.6 of the official CCDV-F exam guide (Version 1.0, effective July 2026). An independent resource, not affiliated with Anthropic; the practice questions are written from scratch.
2.6.1 Why a Claude app can break when nobody changed the code
At Tidewell, a fintech company, eight developers build a payments app for small shops. Its support tool answers merchants' questions about payouts, card fees and chargebacks, and hands the tricky ones to a person. It is built on the Claude Agent SDK, Anthropic's library for building agents. One Tuesday the handoff began to misfire: replies came back longer and laid out differently, and the step that reads each reply to decide on escalation started missing refund disputes. Nobody had merged a change in a week.
Yusuf, the tech lead, found the cause by the afternoon. The tool asked for its model by the alias sonnet, a short name instead of an exact version, and its requirements file named the SDK package without a version. A routine container rebuild installed a newer SDK release. Each SDK release decides which model sonnet means, and the new one meant a newer Sonnet model than the one the team had tested. The code was identical; the model under it was not. Reproducing the fault was harder still, because Claude Code, the coding assistant every developer used, behaved differently on each laptop: different default models, team rules only in Yusuf's personal files, and permission rules that only Katja had.
How a rebuild changed the model
sonnet now names a newer modelA Claude system's behaviour comes from more than its code: which model answers, the prompt it reads, the instruction and settings files that shape each session, and the packages and plugins around them. Any of these can change while the code stays put. Configuration management treats every one of them as a versioned artifact: named exactly, kept in version control, changed through review and shipped as one bundle. Then you can always say what is running, and put back what ran yesterday.
2.6.2 Pin the model: exact IDs versus aliases
Here is the question behind Tidewell's Tuesday, and the exam likes it: when your code names a model, does that name always mean the same model? That depends on the kind of name.
On the Claude API, a model ID such as claude-sonnet-5-5 names one fixed snapshot, a single trained version of the model. Anthropic does not change the weights or configuration behind an existing ID; an updated model ships under a new ID. An alias is a convenience name that points at a model and can be repointed. The API keeps short aliases for models before the Claude 4.6 generation, such as claude-haiku-4-5, which resolves to the latest dated snapshot of that version. Claude Code and the Agent SDK accept family aliases such as sonnet and opus, and those do move: they point to the recommended model and update over time.
| The name you write | Example | What it points to |
|---|---|---|
| Model ID, 4.6 generation and later | claude-sonnet-5-5 |
One fixed snapshot for the life of the ID |
| Dated model ID, earlier models | claude-haiku-4-5-20251001 |
One fixed snapshot |
| Claude API alias, earlier models | claude-haiku-4-5 |
The latest dated snapshot of that version |
| Claude Code and Agent SDK alias | sonnet, opus, haiku |
The family's recommended model, which changes over time |
Memorise the last row: a family alias is a moving pointer. Recognise the first two rows as pinned IDs and the third as a pointer to the newest dated ID. Think of a reading list: "the current edition" gets students different books depending on when they order, while "third edition, 2024 printing" gets everyone the same one. Production code wants the printing.
Pinning buys four things. The same request reaches the same model tomorrow. Your eval, a fixed set of test inputs with a way to score the replies, keeps its meaning, because the model it measured is still the one answering. Upgrades happen when you choose, and rollback has a target: the previous ID. An upgrade then becomes a release like any other: change the ID in the configuration, run the evals and compare with the current release, then roll out. Tidewell pinned claude-sonnet-5, the model it had tested, and planned the move to claude-sonnet-5-5 as a release of its own.
2.6.3 Version the prompt like code
Next came the prompt. The support tool's system prompt lived in a database field that product staff could edit from an admin page to "adjust the tone". It had changed about twenty times in three months. Nobody could say which wording was live on a given day, or bring back last month's.
It is tempting to treat a prompt as content, like the text on a web page. Resist it. A prompt is part of the program: one sentence can change every reply, as surely as one line of code can. Prompt versioning gives it the same treatment as code, in four habits.
- STORE each prompt as a file in version control, with its version in the name or path.
- REVIEW every change in a pull request, like any code change.
- MEASURE each new version against the same eval set as the live one, and ship it only if it scores no worse.
- RECORD the version with every reply in your logs, so any answer traces back to the exact text that produced it.
Rollback then comes almost free: the previous version is still in history, so you redeploy it. Tidewell bundles the model and the prompt into one small committed release file that names both. Look at the model line, which holds a full ID, and at the eval block, which records the score this release earned against the one it replaced.
{
"release": "support-2026-10-06",
"model": "claude-sonnet-5",
"system_prompt": "prompts/support/v15.md",
"eval": {
"suite": "support-golden-v6",
"score": 0.962,
"baseline_release": "support-2026-09-22"
}
}
2.6.4 CLAUDE.md and settings.json: shared by default, personal by exception
The other half of the trouble was on the developers' side: eight people, one repository, eight different assistants. The team rule "money is stored as integer cents, never floats" sat in Yusuf's ~/.claude/CLAUDE.md, which only his sessions read. Two developers had set a different model in their own settings. Katja had once answered "Yes, and don't ask again" to a git push, which saved an allow rule in her local settings file.
Two kinds of Claude Code file matter here. CLAUDE.md files hold instructions in plain Markdown, such as build commands and conventions, and Claude reads them at the start of every session. Files named settings.json hold configuration that Claude Code applies: the model, what it may run without asking, hooks and plugins. Each kind exists at a shared and a personal level, and the rule is short: whatever the team relies on goes in the shared, committed file.
| File | Who it applies to | What belongs in it |
|---|---|---|
CLAUDE.md or .claude/CLAUDE.md in the project |
Everyone, through git | Build and test commands, conventions, architecture notes |
CLAUDE.local.md in the project |
You; add it to .gitignore |
Your sandbox URLs, your test data |
~/.claude/CLAUDE.md |
You, in every project | Personal style preferences |
.claude/settings.json |
Everyone, through git | Team permissions, hooks, plugins, the model |
.claude/settings.local.json |
You; keep it out of git | Personal overrides, trying a change before sharing it |
~/.claude/settings.json |
You, in every project | Theme, your own default model and rules |
An organization can also deploy managed versions of both, which no developer can override or switch off. Learn the committed pair; recognise the others by their local name or home-directory location.
The two kinds combine differently. For a settings key set in several places the highest level wins, while list keys such as permission rules merge. CLAUDE.md files never override each other: every one that applies is loaded, the most specific last.
How the files combine
settings.json: highest level wins
.claude/settings.local.json.claude/settings.json~/.claude/settings.jsonCLAUDE.md: every file loads
~/.claude/CLAUDE.mdCLAUDE.mdCLAUDE.local.md, read lastOne more difference decides where a rule goes. Claude treats CLAUDE.md as context, not enforced configuration. "Never read .env files" in CLAUDE.md is a request Claude will usually follow; a deny rule for Read(./.env) in .claude/settings.json is a rule Claude Code enforces. A rule that must hold goes in settings or in a hook, a script Claude Code runs before a tool call that can block it; CLAUDE.md explains why.
Tidewell moved the cents rule and the build commands into a committed project CLAUDE.md, kept under the 200 lines Anthropic suggests. It committed a .claude/settings.json with the pinned model, the team's permission rules and the .env deny rule. That model key sets the model each session starts on, a shared default rather than a lock: anyone can still switch with /model for one task. Personal habits stayed in local files, and each developer ran /status to check which settings files had loaded.
2.6.5 Plugin and package dependencies: pin what you install
Pinning the model ID fixed the alias, not the rebuild that installed an unreviewed SDK release. That is a dependency problem: anything installed by name alone, with no version, becomes whatever was released last the next time something installs or updates.
For ordinary packages the fix is familiar: a lockfile, or exact versions in the requirements file, committed and updated through review. Tidewell now pins the Agent SDK like every other library, so an SDK upgrade arrives as a pull request whose evals must pass.
Claude Code plugins need the same care. A plugin is a package of skills, agents, hooks and Model Context Protocol (MCP) servers that Claude Code installs as one unit, and it can depend on other plugins. Without a version constraint, a dependency moves to each new release its marketplace publishes the next time users update. If that release renames a tool your plugin calls, your plugin breaks for everyone who updates. Tidewell's team plugin, tidewell-dev, calls the checks in a shared ledger-rules plugin. Look at the dependencies entry in its manifest, .claude-plugin/plugin.json: the range ~1.4.0 accepts 1.4.x patches and never 1.5.
{
"name": "tidewell-dev",
"version": "2.3.0",
"dependencies": [
{ "name": "ledger-rules", "version": "~1.4.0" }
]
}
The range resolves against git tags on the repository that hosts ledger-rules, in the form ledger-rules--v1.4.2, so whoever maintains ledger-rules tags each release. The manifest's own version pins tidewell-dev for its users until the team raises it. Two keys in the committed settings finish the job. The enabledPlugins key turns the plugin on for everyone in the repository, though each developer still installs it once. The extraKnownMarketplaces key registers the team's marketplace, and its autoUpdate flag stays false, the default for third-party marketplaces, so new versions arrive as releases rather than in the background.
2.6.6 One release in every environment
Tidewell's staging had its own hand-edited prompt and installed packages at startup, so "passed in staging" said little about production. Environment parity means development, staging and production run the same versioned bundle. Only the values that belong to an environment differ, and credentials are injected at runtime, never written into any file in the bundle.
What changes between environments
Identical everywhere
claude-sonnet-5prompts/support/v15.mdDiffers per environment
The Agent SDK hides one more trap. By default it loads the same settings files as the Claude Code command-line tool (user, project and local) from whatever machine it runs on. The same code can then behave differently on a laptop than in the production container. In this sketch of Tidewell's handler, look at the three option lines: they take the model and prompt from the release and skip the machine's settings files. The last line logs the release with every answer.
import json, logging, pathlib
from claude_agent_sdk import query, ClaudeAgentOptions
release = json.loads(pathlib.Path("config/release.json").read_text())
prompt = pathlib.Path(release["system_prompt"]).read_text()
options = ClaudeAgentOptions(
model=release["model"], # a full model ID, never "sonnet"
system_prompt=prompt, # the reviewed file this release names
setting_sources=[], # no user, project or local settings files
)
async def answer(ticket_text: str) -> None:
async for message in query(prompt=ticket_text, options=options):
handle(message) # the existing reply and escalation logic
logging.info("answered with release %s", release["release"])
Promotion moves the same tagged release from staging to production unchanged; no fix goes straight into production. When two places disagree, reproduce first: check out the release into a clean copy of the repository and confirm what actually loaded before you touch a prompt.
2.6.7 The exam traps
Every trap here leaves one input to Claude's behaviour implicit, personal or unpinned; the right answer makes it explicit and versioned.
- ✗ Naming the model by an alias in production so it "stays current". ✓ Pin the full model ID and upgrade through a reviewed, evaluated release; an alias can move with no change on your side.
- ✗ Editing the live prompt in a dashboard or a database. ✓ Keep prompts as versioned files, review and score every change, and log the version with every reply.
- ✗ Keeping team rules in one developer's home directory or local settings. ✓ Commit the project CLAUDE.md and
.claude/settings.json, and keep only personal preferences in local and user files. - ✗ Putting a rule that must never be broken only in CLAUDE.md. ✓ Enforce it with a settings permission rule or a hook; CLAUDE.md is context, not enforcement.
- ✗ Letting packages and plugins install "latest" at build time or startup. ✓ Lock package versions, constrain plugin dependencies to tested ranges and turn off auto-update for team marketplaces.
- ✗ Fixing a difference between environments by editing production. ✓ Promote one tagged bundle through every environment, and reproduce from a clean checkout before changing anything.
Four tempting fixes, one real one
2.6.8 Put it together: turn a drifting setup into a versioned one
You now have every piece: pinned model IDs, versioned prompts tied to evals, shared CLAUDE.md and settings files, pinned dependencies and one bundle for every environment. To make them stick, build a tiny versioned setup and break it the two ways Tidewell's broke.
These files connect to skills ahead. Claude Code operation (3.1) covers the features that CLAUDE.md and settings.json switch on. Model selection and tradeoffs (5.3) covers what to test before you move a pinned ID to a newer model. Claude hooks (7.3) enforce what CLAUDE.md can only state, and secrets and key management (7.4) keeps credentials out of every one of these files.
Key takeaways
- ✓ A Claude system's behaviour comes from its model, prompts, instruction files, settings and dependencies as much as from its code, so all of them are configuration to version and review.
- ✓ A model ID names a fixed snapshot; an alias can move, and the
sonnet,opusandhaikualiases in Claude Code and the Agent SDK do, so production pins the full model ID. - ✓ Prompts live in version control as numbered versions, each scored against the same eval set, logged with every reply and rolled back by redeploying the previous version.
- ✓ Team rules go in the committed project CLAUDE.md and
.claude/settings.json, personal preferences in local and user files; settings follow precedence while CLAUDE.md files all load. - ✓ CLAUDE.md is context Claude reads, so a rule that must hold belongs in a settings permission rule or a hook.
- ✓ Lock package versions, constrain plugin dependencies to tested ranges and keep auto-update off for the team's marketplace.
- ✓ Development, staging and production run one tagged bundle in which only environment values differ, and you reproduce from a clean checkout before changing anything.
Check your understanding
4 questions written for this lesson, then one from the CCDV-F question bank on the same topic. Every answer option is explained, including the ones you did not pick. Nothing is stored.
51 CCDV-F questions on Domain 2, free
Every question in the bank is tagged to a domain, so you can drill 51 questions on Applications and Integration alone, or sit the full 53-question timed simulator.
Open the CCDV-F question bank → Back to Domain 2 →
The question bank is free. It asks for an account only because the quiz engine has to store answers to score them and show which domains are weak. The questions on this page need nothing.