Your app starts an agent. Should that agent read your project’s CLAUDE.md, skills, and hooks, or start with a blank slate? Everything the Learn track built lives in files: CLAUDE.md, skills, hooks, settings, subagents. This module answers one question about all of them at once: does an agent your app starts read those files, or not? The answer is one option. Omit settingSources and query() reads the same filesystem configuration the claude CLI reads. Pass settingSources: [] and the session starts sterile. The claude-code-features page is the reference, and the SDK’s own declaration names the three sources: 'user', 'project', and 'local', with a sentence worth quoting exactly because it gates the file everyone forgets is not a settings file: “Must include 'project' to load CLAUDE.md files.” Record 0105 in the workbench repository quotes the full declaration with line numbers.
flowchart TD subgraph iso["settingSources: [] (isolated)"] direction TB a["your options object:<br/>tools, permissions, prompt"] --> b["the session"] end subgraph inh["option omitted (inherited)"] direction TB c["your options object"] --> d["the session"] e["user, project, local settings<br/>CLAUDE.md · skills · hooks"] --> d end iso ~~~ inh
Two shapes of application
The choice tracks who the app is for. A personal tool running in your own project wants to inherit: your CLAUDE.md becomes the agent’s project knowledge, your skills and hooks come along, and the plugin you packaged at the end of the Learn track drops straight into your own app, the two tracks meeting. A multi-user service wants the opposite: sterile config and explicit everything, because “whatever files happen to be on this machine” is not a configuration policy you can state, reproduce, or defend to a second user.
The workbench is the second shape, and since this tag it says so out loud. WORKBENCH_SETTINGS has two values: isolated (the default, settingSources: []) and inherited (the option omitted entirely; record 0105 notes the SDK’s type-declaration file applies the CLI default “when omitted”, and a key set to undefined is not omitted, that is the kind of distinction a test has to assert with "settingSources" in options). The ready frame now carries the choice, and the banner renders it as a sentence a person can act on: “No files on disk steer this agent” or “Files on disk steer this agent”, with an unrecognized value quoted back rather than guessed, because guessing “isolated” would tell a person nothing on disk reaches the agent when something might (records 0113 and 0114).
One thing isolation does not touch: the module 19 tools. readLog and saveNote are objects your process constructs and hands over in the same query() call. Nothing in that path reads a file, so ready.tools says the same thing under both values. Record 0110 walks the citations.
What is read regardless
Two configuration layers sit outside the option, and the evidence differs between them, so here is each with its evidence. Managed policy settings, the machine-level layer an administrator controls, are documented as read regardless of settingSources. The SDK’s declarations state it for settings resolution and imply it for query(). The global ~/.claude.json is documented the same way on the features page, but the type declarations never mention that path at all, so the workbench’s record 0105 carries it as a docs sentence, not a verified behavior. Neither layer is something a per-app option was ever going to override, and that is the point of them.
Which file does which job
The Learn track met these one at a time. Choosing between them is a build-track decision now, so here is the table this course stands behind:
| You want | Reach for |
|---|---|
| Project knowledge the model always has | CLAUDE.md |
| A reusable workflow loaded on demand | Skill |
| Work in a context that cannot pollute yours | Subagent |
| Deterministic interception, no model judgment | Hook |
| A capability from outside the process | MCP server |
Inheriting brings the whole left column to your app for free. The trap below is the price tag.
See the switch
git checkout module-20
WORKBENCH_FAKE_SDK=1 npm run dev
The banner ends with the isolation sentence. Restart with WORKBENCH_SETTINGS=inherited and the sentence flips. In fake mode nothing after the ready frame changes, because the fake reports the setting and simulates nothing, a distinction record 0109 makes so the demo cannot be mistaken for proof of runtime behavior. The runtime effects of settingSources are quoted from the SDK’s documentation, never observed on this box, and every record in this round says so line by line.
Same ladder as always, four steps:
- Run it. Both values of
WORKBENCH_SETTINGS, reading the banner each time. Then runnode scripts/demo-client.mjsfromserver/and find the same fact on thereadyframe. - Read one file.
server/src/sdk-stream.js, thebuildQueryOptionsfunction, with record 0107 beside it: the options became a function so a test with no API key can read what would be sent. - Change one line and see it. Start the server with
WORKBENCH_SETTINGS=banana. It refuses to boot, naming the two allowed words: an unknown mode is a configuration error, not a guess. - Build. The banner sentence names what inherited mode reads but not where from. Extend it with the workspace path already on the
readyframe, so the sentence says whose files steer the agent. Client tests stay green:npm test -w client.
You flip the workbench to inherited because you want your CLAUDE.md in the agent’s head, and everything works. Here is the loop you switched on, walked in record 0106. The agent’s working directory is workspace/, and the agent can write there, that is the application’s whole point. From the SDK’s side, ‘project’ means .claude/settings.json relative to the working directory. So: prompt one asks the agent to write workspace/.claude/settings.json with a hooks block in it, and nothing happens, because the running session read its settings at start. Prompt two, which looks exactly like any other prompt, starts a new session that reads the file prompt one wrote, and the hook, a shell command running at your process’s trust level, fires. The same walk works through workspace/CLAUDE.md as steering text. The person typing never sees a difference between the two prompts.
strictMcpConfig from module 19 already closes exactly one branch of this, the workspace .mcp.json, and only that branch. The general rule: inherited settings plus an agent-writable settings directory is a loop, so isolate any app whose workspace the agent writes, and inherit only where the configuration directory is one the agent cannot touch. That is why isolated is the workbench default rather than a hardening option.
The question travels everywhere. Every agent framework has ambient configuration, and “does the runtime read config out of the directory the agent writes” is worth asking of all of them. The write-config-now, obeyed-next-session loop is a general vulnerability shape, an SDK quirk it is not. What does not travel is the mechanism: settingSources, the three-source union, the CLAUDE.md gating sentence, and the managed-policy exception are this SDK’s own. The features page against your installed pin is the reference.
Check yourself
- A teammate’s service passes
settingSources: undefined“to get the default” and their sterile deployment starts reading project settings. What happened, and what is the one-word fix? - Your inherited-mode app stops loading
CLAUDE.mdafter someone narrowssettingSourcesto['user', 'local']. Which quoted sentence from this page predicted that? - Name the branch of record 0106’s attack that
strictMcpConfigcloses, the branches it does not, and the property of the workspace that makes the whole attack possible.