Your environment profile and permissions
Finish with two files: a filled environment profile, and a permissions file whose behaviour on six commands you wrote down before you observed it.
Setup
Every command after the setup block runs from inside day2-work, the copy of your
lane at the course root; the shipped sandbox/ is never edited. Paste the block from
the course root (if your shell is inside day2-work, cd .. first). It
makes the copy if it is missing, installs the blank profile, the prediction grid and the example
permissions file, and leaves you inside the copy. Nothing here needs Exercises 1 or 2.
mkdir -p work && { [ -d day2-work ] || cp -R sandbox/swift day2-work; } && cd day2-work && mkdir -p .claude && cp ../day-2-loops-and-your-environment/starter/env-profile.md . && cp ../day-2-loops-and-your-environment/starter/prediction-grid.md . && cp ../day-2-loops-and-your-environment/starter/settings.example.json .claude/settings.json && ls env-profile.md prediction-grid.md .claude
mkdir -p work && { [ -d day2-work ] || cp -R sandbox/kotlin day2-work; } && cd day2-work && mkdir -p .claude && cp ../day-2-loops-and-your-environment/starter/env-profile.md . && cp ../day-2-loops-and-your-environment/starter/prediction-grid.md . && cp ../day-2-loops-and-your-environment/starter/settings.example.json .claude/settings.json && ls env-profile.md prediction-grid.md .claude
The block lists env-profile.md, prediction-grid.md, and
settings.json under .claude. JSON carries no comments, so the
rule-by-rule reading of the example lives in
../day-2-loops-and-your-environment/starter/settings.example.md; step 4 says when to
open it. The example replaces the settings.json your lane shipped, and its first
two keys, claudeMdExcludes and autoMemoryEnabled, are that file's,
carried over. They are not permission rules; leave them as they are.
Steps
-
Fill the six rows of
env-profile.md, one short true sentence each. Start from the Default wording column of its table and change what is false for you. Name the kind of connector, not the vendor: no employer, internal system, hostname or repository name. A connector that later needs a rule of its own is scoped asmcp__<server>,mcp__<server>__*, ormcp__<server>__<tool>. -
Edit
.claude/settings.json: the deny list first, then ask, then allow, which is the order the engine evaluates rules in. Each rule is shapedTool(pattern). Keep every shipped rule, including any allow rule you cannot yet justify; step 4 and the reference answers come back to those. You may add rules, but write no allow rule of your own you cannot justify in one sentence. The only rules you remove are the other lane's two allow rules (Bash(swift test:*)andEdit(./Sources/**)for Kotlin,Bash(./gradlew test)andEdit(./src/**)for Swift), since the example carries both lanes' and those match nothing in your copy. Leavesettings.example.mdclosed until your predictions exist. -
Fill all six rows of
prediction-grid.mdbefore you run anything. For each command writeallow,askordeny, and name the rule you think decides it. Predictions written afterwards teach nothing. -
With the predictions written, open
settings.example.mdfor the shipped reason behind any rule. Then check that the file parses and that three deny rules survived your edit: probes 4, 5 and 6 run into them.Swift lanepython3 -m json.tool .claude/settings.json grep -nE 'Bash\((rm|gh pr merge|curl):\*\)' .claude/settings.jsonKotlin lanepython3 -m json.tool .claude/settings.json grep -nE 'Bash\((rm|gh pr merge|curl):\*\)' .claude/settings.jsonThe first command prints your settings back, reformatted. The second prints the lines carrying the three rules, which in the unedited example are:
7: "Bash(rm:*)", 8: "Bash(gh pr merge:*)", 12: "Bash(curl:*)",Then take a baseline in your own shell, which passes through no permission rules: a green suite now means a red one during the probe is the settings file, not the code.
Swift laneswift testThe XCTest count line, the one starting
Executed, decides it. It is not the last line: Swift Testing prints its own summary after it,Test run with 0 tests, which counts none of these tests, so ignore that one and look a few lines up. A fresh copy ended with these lines on 2026-10-06:Test Suite 'All tests' passed at …. Executed 34 tests, with 1 test skipped and 0 failures (0 unexpected) in … (…) seconds … Test run started. … Testing Library Version: … … Target Platform: … … Test run with 0 tests in 0 suites passed after … seconds.If your copy carries Exercise 1's change, the count is higher than 34; any count with 0 failures is the green baseline.
Kotlin lane./gradlew testBUILD SUCCESSFULis the line that decides it. A first run in a fresh copy printed the lines below on 2026-09-23; a repeat run prints different task counts.BUILD SUCCESSFUL in … … actionable tasks: … Consider enabling configuration cache to speed up this build: https://docs.gradle.org/…/userguide/configuration_cache_enabling.htmlA suite an earlier exercise left red is fine; note it. Now start Claude Code inside
day2-workwithclaudeand look at the rules the session loaded./permissionsThat view is the rules in effect for this session, which is not always the file you just edited. If one of the three deny rules is not in it, put it back before you send the probe.
-
Send the probe, in full, and record what happened for each of the six commands.
Swift laneAttempt each of these six commands in order, one at a time, in this working copy. Do not work around a refusal, and do not edit any settings file. After each one, tell me whether it was allowed, asked, or denied, and quote the rule that decided it. If one is refused, record the refusal and go on to the next. 1: git status. 2: swift test. 3: gh pr view 1. 4: gh pr merge 1. 5: rm -rf build. 6: curl https://example.comKotlin laneAttempt each of these six commands in order, one at a time, in this working copy. Do not work around a refusal, and do not edit any settings file. After each one, tell me whether it was allowed, asked, or denied, and quote the rule that decided it. If one is refused, record the refusal and go on to the next. 1: git status. 2: ./gradlew test. 3: gh pr view 1. 4: gh pr merge 1. 5: rm -rf build. 6: curl https://example.comRows 1 and 3 test rules, not repositories, so ignore whatever
git statusandghprint. Row 3 asks for approval: choose Yes, not “Yes, and don't ask again”.gh pr viewonly reads, and the row isaskwhatever it prints. The Observed column takes what happened, not what the agent said should happen. -
Compare the two halves of the grid row by row and mark every mismatch with one sentence: what you believed about the rules, and what turned out to be true. Then put both deliverables where the reset cannot reach them.
cp env-profile.md prediction-grid.md ../work/ && ls ../work -
Only now, open the reveal below.
-
Part B, observation only; skip it if you have no connectors attached. Run these in order, disabling one connector in the
/mcpview, and write down the two/contextfigures and their difference./context /mcp /context
If you see something else instead
Likely wrong turns
- A line naming a line and a column where the reformatted file should be. A comma left after
the last deny rule printed
Illegal trailing comma before end of array: line 14 column 25 (char 309)under Python 3.14 on 2026-09-23; the wording varies by version. The file is malformed: fix it and check again before you start a session. - The agent runs
rm -rf buildwithout stopping. The deny rule coveringrmis not among the rules this session loaded: it came out during your edit, or the session started outsideday2-workand read a different.claude/settings.json. Quit, run step 4's checks, start again. /permissionsshows rules you never wrote. Something above your file has an opinion. Settings resolve in a fixed order: a managedmanaged-settings.jsonfrom MDM or the console, thenclaude --settings, then.claude/settings.local.json, then.claude/settings.json, then~/.claude/settings.json.- You chose “Yes, and don't ask again” on row 3. Record the row as
askanyway. The rule went into a.claude/settings.local.json, not the file you edited; the reveal says where that file is and what the rule changes./permissionsshows it. - The agent stopped after row 3 and is waiting for you. Send:
Go on with commands 4, 5 and 6, as before. - All six rows match. That is a pass if you wrote every prediction in step 3; row 5 is the one built to catch a first reading of the file.
- To start the settings file over, copy the example in again rather than resetting: the reset deletes your profile and grid with the copy.
Self-check
Four lines decide it
Run them in your own shell, from inside day2-work.
python3 -m json.tool .claude/settings.json > /dev/null && echo 'settings parse: ok'
grep -c '^[0-9]\. \*\*.*<' env-profile.md
awk -F'|' '/^\| [1-6] \|/ && $4 ~ /allow|ask|deny/ {n++} END {print n+0}' prediction-grid.md
grep -c 'gh pr view' .claude/settings.json
A pass is settings parse: ok, then 0, then 6, then
0. The second counts profile rows still holding their placeholder; the third counts
grid rows with a word in the Predicted column. The fourth prints 0 unless you
wrote a gh pr view rule yourself in step 2, because an accepted “don't ask again” is never saved to the
shared file, wherever its settings.local.json ended up.
Done when: all six profile rows are filled and none
names an employer, a system or a repository; the settings file parses, still carries every shipped rule
except the other lane's two allow rules, and every allow rule you added has a sentence you would
defend in review; all six grid rows carry a prediction written before its
observation, each mismatch annotated with the rule that decided it; and copies of both files are
in ../work/.
Reference answers
Row 5, and the rule row 3 can leave behind
Row 5, rm -rf build. This is the row built to disagree with a
first reading of the file. The example allows
Bash(rm -rf build) and denies Bash(rm:*). The allow rule reads as
housekeeping and does nothing: evaluation runs deny, then ask, then allow, and the first match
ends the decision regardless of how specific a later rule is. Specificity is never a tiebreak.
The row is deny, decided by Bash(rm:*), and the narrow allow underneath
it never runs.
That is the failure mode worth carrying out of this exercise. A rule that is written, read in
review, and approved can still be inert, and nothing in the file says so. The only way to know is
to predict a command and then run it. Row 4, gh pr merge 1, is the same mechanism
pointing the way people expect: Bash(gh pr merge:*) in the deny list settles it
before the Bash(gh:*) ask rule is ever consulted.
Row 3, gh pr view 1. The row is ask, decided by
Bash(gh:*). The rule to watch is the one an accepted “Yes, and don't ask
again” saves. It is an allow rule, and in day2-work it changes nothing: the ask
rule Bash(gh:*) is evaluated first, the same mechanism as row 5. In a copy without
that ask rule, it takes effect. It is not saved into the .claude/settings.json you
edited: Claude Code saves it to .claude/settings.local.json at the root of the git repository, and it applies to
future sessions anywhere in that repository. Four cases keep the file beside
day2-work/.claude/settings.json instead, where the reset removes it with the copy:
outside a git repository, as in a zip download; on Windows; when the repository root is your
home directory; and when the repository root or its .git or .claude
entry is not owned by your user. Otherwise the file is at the repository root, which in a clone
of this course is the clone's root folder, not day2-work, so the rule outlives the
reset and follows you into Day 3's copy. File-edit approvals are never saved; they last until
the session ends.
Wherever it lands, .claude/settings.local.json is personal and outranks the
shared .claude/settings.json, so a rule that lives only there is one your teammates
do not have and will not see in your diff. A rule the team shares is written into the shared file
on purpose, where a reviewer gets to ask what it lets through.
One more thing before you grep your own file: Bash(gh pr view *) and
Bash(gh pr view:*) are the same rule in two spellings, so searching for one form
can miss a rule that is already there.
Part B: what to expect from the connector probes
This course quotes no /mcp or /context output, on purpose. Your
connectors do not exist on the machine this course was built on, and the documentation read on
2026-09-22 does not state the format of the context breakdown, so any example printed here would
be invented. What you record is your own two figures.
Each attached server's tool names are in the window from the start of the session, with the
full schemas deferred by default. This course has not recorded what disabling a server partway
through a session does to the figures, so the pair you wrote down is the finding, whatever it
shows. If the two figures match, check that /mcp showed the server as attached
before you disabled it.
If /mcp shows nothing attached, there is nothing here to measure. Write the
honest answer in row 1 of your profile, which is the row that matters most, and move on to
Exercise 4.
Reset
Every Day 2 exercise resets the same way: quit Claude Code, then from inside
day2-work run your lane's line.
cd .. && rm -rf day2-work && cp -R sandbox/swift day2-work && cd day2-work
cd .. && rm -rf day2-work && cp -R sandbox/kotlin day2-work && cd day2-work
It replaces the copy with a fresh one from the shipped lane and leaves ../work
alone, so step 6's copies survive. A fresh copy is the shipped lane and nothing else, so re-run
this page's setup block before you continue. It does not reach a
.claude/settings.local.json at a clone's root folder; in the four cases the reveal
lists, the file is inside day2-work and goes with the copy. You need not reset
before Exercise 4, which edits the
settings file you wrote here.
Sources
cc-core-21,cc-core-22,cc-core-24,enterprise-07,enterprise-10— theTool(pattern)rule format and its space-star spelling, deny then ask then allow with the first match winning,/permissions, andmcp__<server>__<tool>scoping.cc-core-39— where “Yes, and don't ask again” saves its rule, how far it applies, and that file-edit approvals are not saved.cc-core-41— the directory you launch Claude Code in is the session's primary working directory, accessed 2026-09-23. From Permissions.cc-core-23,enterprise-11— settings file precedence, and the shared.claude/settings.jsonagainst the personal.claude/settings.local.json.cc-core-39— the four cases in which a saved approval stays beside.claude/settings.json. From Settings.cc-automation-11—/mcp, from Connect Claude Code to tools via MCP.cc-core-18—/context, from Slash commands.cc-core-36— MCP tool names load at session start with schemas deferred by default, from Context window.cc-core-32— Claude Code 2.1.280, from the Claude Code changelog.- Quoted output was recorded on 2026-09-23 in a fresh copy of each lane made by this page's setup block; the Swift baseline's tail was re-recorded on 2026-10-06.