From 823ce4222532b4060457388890bb466a86c381b4 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Wed, 16 Sep 2026 21:58:18 +0200 Subject: [PATCH 1/2] chore(config): default model fable 5.1 --- settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.json b/settings.json index b1193df..17c0706 100644 --- a/settings.json +++ b/settings.json @@ -250,7 +250,7 @@ "disableBypassPermissionsMode": "disable", "additionalDirectories": [] }, - "model": "opus[1m]", + "model": "claude-fable-5-1[1m]", "hooks": { "SessionStart": [ { From 5eccc3f1c490d75e07908dfd4ab088dcd162a285 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Wed, 16 Sep 2026 22:03:23 +0200 Subject: [PATCH 2/2] feat(automode): docker and node framed by the classifier, ask rules retired --- .claude/tasks/TODO.md | 24 ++++++++++++++++++++++++ CHANGELOG.md | 15 +++++++++++++++ settings.json | 14 ++++++++------ templates/settings/SETTINGS.md | 17 +++++++++++++++-- 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/.claude/tasks/TODO.md b/.claude/tasks/TODO.md index 7404bef..4679e5c 100644 --- a/.claude/tasks/TODO.md +++ b/.claude/tasks/TODO.md @@ -1,5 +1,29 @@ # TODO +## 2026-09-16 — docker + node framed by the classifier (feature/automode-docker-node) +User: `docker exec -i supabase_db_game psql … -f - < supabase/verify/*.sql | tail` +must run unprompted under auto mode; same for node/npm/npx when the package +is declared and effects stay in the cwd; "ajoute du soft deny pour bien le +cadrer". Findings: `ask` is inert under auto (LRN-146 re-verified on 2.1.273 +with a `node -e` probe; the docs claim otherwise for content-scoped rules); +the real gate is the built-in `Remote Shell Writes` / `Production Reads` +classifier rules; a static `Bash(node *)` allow rule is suspended under auto +(wildcarded interpreter), so `autoMode.allow` prose is the only lever for +node. User approved the design and the `ask` removal explicitly (S6 override +for this change, diff reviewed on the branch). +- [x] A1 `settings.json` — drop 4 docker + `node -e` from `ask`; new + `autoMode.allow` (`$defaults` + local dev containers + project-local + node); 2 `soft_deny` entries (docker data destruction, undeclared + node packages); `model` bump committed separately +- [x] A2 `templates/settings/SETTINGS.md` — `autoMode.allow` tier row + + why a static interpreter allow rule cannot do it; LRN-146 re-verify note +- [x] A3 CHANGELOG [Unreleased] Changed +- [x] A4 verify (2026-09-16, all green; `critique` printed nothing): `jq`, `claude auto-mode config`, + `doctor.sh`, live `docker exec` in game +- [ ] A5 registries at capitalize: LRN (doc vs observed `ask` under auto, + 2.1.273; `autoMode.allow` = exception tier; wildcarded-interpreter + allow suspended), BDR-090 addendum + ## 2026-09-15 — align config + deployment on the hand-edited settings.json (feature/automode-config-alignment) User edited global `settings.json` by hand: 4 destructive rules moved deny→ask (`rsync`, `kill -9`, `killall`, `pkill`), 4 removed from ask diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ff4bbf..66d8a50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,21 @@ Format follows [Keep a Changelog](https://keepachangelog.com/). destructive command under auto mode. ### Changed +- **Docker and node go through the classifier with a framing, instead of + an inert `ask` tier.** `Bash(docker run|exec *)`, `Bash(docker[-| ]compose + up*)` and `Bash(node -e *)` leave `permissions.ask` (no prompt under auto + mode, re-verified on 2.1.273). A new `autoMode.allow` list, `$defaults` + first, names the two routine cases the built-in `Remote Shell Writes` / + `Production Reads` rules were catching: `docker exec`/`run`/`compose` + against a local dev container whose name does not carry `prod`, running a + SQL file or script from the repo inside it; and project-local node + (`node `, `npm run`, `npx`/`pnpm exec` of a lockfile-declared + package, effects inside the cwd). Two `soft_deny` entries frame what that + opens: docker data destruction (`rm -f`, `volume rm`/`prune`, `system + prune`, `compose down -v`, `--privileged`, bind mounts outside the cwd) + and undeclared node packages (`npx`/`dlx` of a package absent from the + lockfile, `npm install `). `SETTINGS.md` gains the `autoMode.allow` + tier and the reason a static `Bash(node *)` rule cannot do this job. - **The classifier, not `permissions.ask`, now guards destructive shell work** (BDR-090). Ten rules left the static tiers: `rsync`, `kill -9`, `killall`, `pkill` out of `deny`, and `python3 -c`, `python -c`, diff --git a/settings.json b/settings.json index 17c0706..4e55d4f 100644 --- a/settings.json +++ b/settings.json @@ -224,13 +224,8 @@ "Bash(curl * | sh)", "Bash(wget * | sh)", "Bash(mkfifo *)", - "Bash(node -e *)", "Bash(git push *)", "Bash(git push)", - "Bash(docker run *)", - "Bash(docker exec *)", - "Bash(docker-compose up*)", - "Bash(docker compose up*)", "Bash(brew install *)", "Bash(apt install *)", "Bash(apt-get install *)", @@ -360,6 +355,11 @@ "inputNeededNotifEnabled": true, "skipAutoPermissionPrompt": true, "autoMode": { + "allow": [ + "$defaults", + "Local dev containers: `docker exec`, `docker run`, `docker compose up`/`exec`/`logs`/`ps` against a container running on this workstation whose name does not carry `prod` or `production` (a local Supabase or Postgres such as `supabase_db_*`) is routine development, not a remote shell into a shared host. Running a SQL file or script that lives in the repo inside it (`psql -f`, migrations, verify scripts) and piping the output through `tail` or `grep` passes. Remote Shell Writes, Production Reads and Sensitive Remote Exec apply only to hosts named as sensitive in Environment or carrying `prod`. A literal `DROP`, `TRUNCATE` or `DELETE` without a predicate typed on the command line stays under Mass Delete.", + "Project-local node: `node `, `npm run`, `pnpm` or `yarn` scripts, and `npx` or `pnpm exec` of a package declared in the project's manifest or lockfile, with effects inside the current working directory, pass like `awk` or `echo`. `node -e` that computes or edits inside the working directory passes; the soft block on inline interpreters that delete or write outside it still applies." + ], "soft_deny": [ "$defaults", "Scope of intent: an instruction clears a SOFT BLOCK for the current turn only. An approval given in an earlier turn is not an approval now, and the same action repeated in a later turn has to be asked for again.", @@ -368,7 +368,9 @@ "Sending SIGKILL (`kill -9`) or killing processes by name (`killall`, `pkill`). These reach processes outside this session, including the user's editors, shells, dtach sessions and background jobs, and the target is chosen by a pattern, so a typo kills the wrong thing. Clear only when the user named the process in this turn.", "Editing more than one file in place in a single command: `sed -i` or `perl -pi` over a glob, or a loop over `git ls-files`. The damage is not loss, since git recovers it, but a diff spanning hundreds of files that nobody reads before committing. `sed -i` on a single named file passes. Clear only when the user asked for the sweep.", "Moving or renaming a directory inside the repo (`mv src/api src/api_old`, or any `mv` of a tree). It breaks imports and paths silently, and the breakage surfaces far from the command. Clear only when the user asked for that move.", - "An inline interpreter or `xargs` that deletes, or that writes outside the current working directory: `python3 -c`, `python -c` or `node -e` calling `rmtree`, `remove`, `unlink` or `truncate`; `xargs` feeding `rm`, `mv` or `dd`. `find ... | xargs rm` is the case that matters, since it routes around the `find * -exec rm` deny rule. Reading, computing, and editing a file inside the working directory pass untouched." + "An inline interpreter or `xargs` that deletes, or that writes outside the current working directory: `python3 -c`, `python -c` or `node -e` calling `rmtree`, `remove`, `unlink` or `truncate`; `xargs` feeding `rm`, `mv` or `dd`. `find ... | xargs rm` is the case that matters, since it routes around the `find * -exec rm` deny rule. Reading, computing, and editing a file inside the working directory pass untouched.", + "Docker data destruction on this workstation: `docker rm -f`, `docker volume rm` or `prune`, `docker system prune`, `docker compose down -v` (drops named volumes, which hold local database data with no undo), and `docker run` with `--privileged` or a bind mount outside the current working directory. Clear only when the user named the container or volume in this turn.", + "Undeclared node packages: `npx `, `pnpm dlx` or `yarn dlx` of a package absent from the manifest and lockfile runs code fetched at call time; `npm install ` or `pnpm add ` adds a dependency the house rule requires naming first. Clear only when the user named the package in this turn." ], "hard_deny": [ "$defaults", diff --git a/templates/settings/SETTINGS.md b/templates/settings/SETTINGS.md index ab1a3de..d62d659 100644 --- a/templates/settings/SETTINGS.md +++ b/templates/settings/SETTINGS.md @@ -79,8 +79,11 @@ one repo feeds the classifier false facts in all the others. ### `ask` is not a prompt under auto mode -Verified in-session (LRN-146): with `defaultMode: auto`, Bash rules in -`permissions.ask` were auto-approved and raised no prompt. `deny` is the only +Verified in-session (LRN-146, re-verified on 2.1.273 on 2026-09-16 with a +`node -e` probe matching an `ask` rule): with `defaultMode: auto`, Bash rules +in `permissions.ask` were auto-approved and raised no prompt. The auto-mode +docs claim the opposite for "content-scoped" rules such as `Bash(git push *)`; +the observed behavior wins until a probe shows a prompt. `deny` is the only tier the classifier cannot lift. So for a destructive command you want gated but still reachable, `ask` is the @@ -94,11 +97,21 @@ it. Keep `deny` for what must never run at all. | Never runs, no exception, matchable by a command pattern | `permissions.deny` | | Never runs, and a pattern cannot express it (a read then a send, a prod target) | `autoMode.hard_deny` | | Runs when the user asks for it, blocked otherwise | `autoMode.soft_deny` | +| Runs freely when a condition holds that only the classifier can judge (a local dev container, a package declared in the lockfile) | `autoMode.allow` | | Runs freely | `permissions.allow`, or nothing | `permissions.ask` is not on this list on purpose. Under `defaultMode: auto` it gates nothing. +`autoMode.allow` is the exception tier: inside the classifier an `allow` entry +overrides a matching `soft_deny`, built-in or yours, so word it as narrowly as +the condition allows. It is also the only tier that can open an interpreter: +under auto mode Claude Code suspends the static allow rules that grant +arbitrary code execution (`Bash(*)`, wildcarded interpreters such as +`Bash(node *)`), so those commands reach the classifier whatever +`permissions.allow` says. `awk` and `echo` pass through a static rule; `node` +cannot. + ### Scope of intent A `soft_deny` clears on the user's instruction, and this config scopes that to