# bots/safety: the one restriction every agent keeps

User decision, 2026-09-29: "lift all restrictions except for removing user folders." No agent runs in a sandbox or with
allow lists any more. `protect_user_folders.py` is what remains. It blocks:

- deleting or moving `C:\Users`, a profile root, or a top-level profile folder (Desktop, Documents, .codex, AppData, ...)
- recursive deletes anywhere inside a profile, except `AppData\Local\Temp`

Everything else is allowed.

| Agent | Where the guard is registered |
|---|---|
| Claude Code (claude_cli) | `~/.claude/settings.json` hooks.PreToolUse, matcher `Bash\|PowerShell` |
| Grok Build (grok_build) | `~/.grok/hooks/protect_user_folders.json` (it also reads Claude's settings) |
| ChatGPT Codex (chatgpt_codex) | `~/.codex/hooks.json`; headless runs pass `--dangerously-bypass-hook-trust`, interactive: trust once in `/hooks` |
| Qwen Code (qwen_code) | `~/.qwen/settings.json` hooks.PreToolUse, matcher `run_shell_command` |
| Antigravity CLI (antigravity_cli) | `~/.gemini/config/hooks.json` (its own JSON protocol: `toolCall.args.CommandLine` in, `{"decision": "deny"}` out). Regex `permissions.deny` in settings.json did NOT hold in the 2026-09-29 probe, so it is not used |

Antigravity splits the hook `command` on spaces and keeps the quotes, so a quoted `"C:/My Projects/..."` path fails on
EVERY call and blocks every tool (the 2026-09-29 preflight passed for that wrong reason). Every tool's hook therefore uses the
space-free 8.3 path: `python C:/MYPROJ~1/BUILDB~1/bots/safety/protect_user_folders.py` (Antigravity fixed 2026-09-29; Qwen Code hit the same bug on 2026-09-30 ("Tool run_shell_command was not run"
47 minutes long), so Qwen, Codex and Grok were switched too; Claude Code runs hooks through a shell and keeps the quoted path;
the preflight now shows the real BLOCKED message and the agent can run other commands). A preflight PASS alone does not
prove the guard ran: also check that the agent can run a harmless command.

Hook TRUST (2026-09-30): Codex stores a trusted hash per hook ([hooks.state] in ~/.codex/config.toml). Changing a hook's
command (as the 8.3 switch above did) makes it "new or changed": interactive Codex then runs WITHOUT the guard until it is
re-trusted (/hooks: PreToolUse Installed 1, Active 0). review_loop.py passes --dangerously-bypass-hook-trust, so the
headless preflight still PASSES and hides this. After every hook edit: open each tool interactively, review the hook,
trust it, and confirm Active 1 (educational/ONBOARDING.txt checklist step 7b).

`python test_hook_protocols.py` feeds the guard every stdin protocol (including Cursor-style
`beforeShellExecution`) and checks the answers.

Grok Bot (desktop app) has NO working guard (live test 2026-09-29, `grok_bot_probe.py`, two runs with a restart between).
It runs shell commands on this PC and ran a recursive delete of the `.bbb_probe` test folder without asking. Its engine
contains Cursor's hook code, but hook files in `~/.cursor/hooks.json`, `~/.grokbot/hooks.json` and the project's
`.cursor/hooks.json` were never called; the test files were removed afterwards. Treat Grok Bot as unguarded: project
folders only, no profile clean-up tasks, backups. Re-run the probe after Grok Bot updates (it needs the app started with
`--remote-debugging-port=9333`; relaunch without the port afterwards).

Checks:

    python protect_user_folders.py --self-test                     # built-in cases
    python protect_user_folders.py --check "rm -rf ~/Documents"    # one command
    python ../conversation_hub/review_loop.py --preflight <login>  # the real agent tries to delete ~/.bbb_probe; must survive
    python check_agent_env.py --all                                # the hook runs inside every LIVE agent window (exit 0)

`check_agent_env.py` (2026-10-02, hub #1071) exists because preflight runs headless and inherits the caller's PATH. On
2026-10-02 the windows launched from the All Builds page had a Store "Python Install Manager" python first on PATH, so
every hook failed with "Access is denied." (exit 1). See `educational/AGENT_LAUNCH_ENVIRONMENT.txt`.

Backstops: the profile guard in review_loop.py and hub_watch.py kills an agent's process tree if key profile folders
disappear, and the twice-daily profile backups go to `D:\Backups\llm-profiles`. Blocks are logged to
`logs/protect_user_folders.log`.

False blocks (user decision 2026-09-29: keep them, do not add exceptions). The guard judges text, so a command that only
MENTIONS a protected delete is blocked too, e.g. a heredoc writing a hub message that quotes the probe command. 3 of the
first 25 blocks were like that; nothing ran and nothing was lost. Fix on your side: write the text with your editor/file
tool (Write/Edit), then pass the file (hub.py post --file). Do not look for a way around the guard.

Known limit: the hook sees the command text. A script that computes a profile path at run time (for example
`python cleanup.py`) is not visible to it, and neither is a variable (`Remove-Item -Recurse $tmp`, `rm -rf "$WORKDIR/"`,
`$env:GROK_HOME`): all allowed (checked 2026-09-29). The #30 wipe was most likely that shape (a temp-folder delete whose
variable held C:\Users\aidat; GROK_HOME was the profile), so this guard would probably NOT have stopped #30. The
process-tree kill and the backups cover that case; courses must say so.
