Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .claude/agents/implementer.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ Never call bare `gh`. EVERY `gh` invocation (PR create/update, comments, `gh api
1. **Explore, don't guess.** Delegate codebase discovery to the `Explore` subagent to map the files you'll touch. Stay read-only until you understand the area.
2. **Respect your boundary.** You were assigned a module/path. NEVER edit files outside it. If the task truly requires touching another module, stop and report back to the orchestrator — do not reach across the boundary.
3. **Implement in small commits.** Match surrounding code style. Write/extend tests alongside the change.
**Stage explicit paths only — never `git add -A` / `git add .` / `git commit -a`.** A sandboxed session
masks sensitive config paths (shell rc, `.gitconfig`, `.mcp.json`, `.claude/{hooks,skills,routines}`,
editor dirs) as `/dev/null` character-device nodes; `git status` shows them as untracked, and a blanket
`git add` can try to index a device node and abort your commit. Add the files you actually changed, by
name. Ignore any `crw-` device-node entries `git status` shows — they are sandbox masks, not your work.
4. **Self-gate before declaring done.** Run, in order, the commands from `.claude/gates.json`: `build` → `lint` → `typecheck` → `test_affected` → `coverage`. Use `.claude/scripts/gate.sh <name>` if present. Fix anything that fails. Do not report done with a red gate.
5. **Open a PR** (or leave the branch ready, per `CLAUDE.md` merge policy).
6. **Report back** in this format:
Expand Down
1 change: 1 addition & 0 deletions .claude/agents/orchestrator.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ EVERY `gh` invocation — by you and by every agent you spawn — MUST go throug
- Give every worker a crisp objective, an explicit file/module boundary, an output format, and the exact gate commands. Vague delegation produces overlap and rework.
- Never spawn more than `max_parallel_workers` at once.
- Keep your own context clean: delegate exploration to the `Explore` subagent (read-only, cheap), not yourself.
- Git hygiene: tell workers to **stage explicit paths, never `git add -A`/`git commit -a`**. A sandboxed session masks config paths (shell rc, `.gitconfig`, `.mcp.json`, `.claude/{hooks,skills,routines}`, editor dirs) as `/dev/null` device nodes that show up in `git status`; a blanket add can abort the commit. They're expected artifacts, not the worker's changes (see `docs/HARDENING.md` → Caveats).

## Status report format (your "standup")
```
Expand Down
49 changes: 49 additions & 0 deletions .claude/commands/harden.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
description: Arm hands-off mode — write the bypass + strict-sandbox profile into settings.local.json (per docs/HARDENING.md)
---

You are arming this project for **hands-off (autonomous) operation**: `bypassPermissions` + a strict OS
sandbox + an always-on deny list, so the PR loop can run without a human approving every command.
`docs/HARDENING.md` is the source of truth — this command materializes its **Step 1** config and then
tells the human which steps only they can finish (sudo / OS-level / restart).

> Run this **before** hardening (while still in the safe interactive mode). Once the hardened profile is
> active, the agent can no longer edit `.claude/settings*.json` (it's in the Edit/Write fence) — by design.

Do these in order. Stop and report if any step can't be completed.

## 1. Preconditions
- Read `docs/HARDENING.md` — its Step 1 JSON block is the canonical config; use it verbatim as the base.
- Confirm the sandbox backend: `command -v bwrap socat`. If either is MISSING, do NOT enable strict mode
blindly — report it and point the user at HARDENING.md Step 2 (`sudo apt-get install -y bubblewrap socat`).
You can still write the file, but warn that `failIfUnavailable: true` will refuse to start until they're installed.

## 2. Ensure `.claude/settings.local.json` is gitignored
- Check `git check-ignore .claude/settings.local.json`. If NOT ignored, append `.claude/settings.local.json`
to `.gitignore`. Bypass is a per-machine decision and must never be inherited by a clone.

## 3. Write / merge the hardened profile into `.claude/settings.local.json`
- Materialize the **Step 1 block from `docs/HARDENING.md`**: `permissions.defaultMode = "bypassPermissions"`,
the full `permissions.deny` list (privilege escalation, container escape, raw-network/exfil, publish,
`gh auth token`/`secret`, force-push/`rm -rf`, secret-file Reads, WSL host interop, and the portable
Edit/Write fence), and the strict `sandbox` block (`enabled: true`, `allowUnsandboxedCommands: false`,
`failIfUnavailable: true`, the `denyRead`/`credentials` lists).
- **Merge, don't clobber:** if `settings.local.json` already exists, preserve any existing
`permissions.allow` entries and merge the deny list (union, no dupes). Keep the result valid JSON.
- Do **not** touch the committed `.claude/settings.json` — it stays at the safe interactive default, and
the harness owns it at runtime.
- Honor the `.env` gotcha: `.env` is denied to the **Read tool** but must **not** be added to
`sandbox.credentials`/`denyRead`, or the loop scripts can't source `GH_BOT_TOKEN`.

## 4. Report what only the human can finish
Print a short checklist of the steps this command cannot do (they need a real terminal / sudo / restart):
- **Restart Claude Code** — bypass + sandbox changes don't hot-reload, and bypass needs the dangerous-mode
dialog accepted once. Then run `/sandbox` to confirm the backend resolves.
- **OS-level isolation (HARDENING.md Step 2)** — cut host bridges (on WSL: `/etc/wsl.conf` automount+interop
off), drop the agent user from `docker`/`sudo` groups, prefer a dedicated disposable distro/VM.
- **Verify the backstop bites:** after restart, ask the agent to run `sudo true` (should be **blocked**
under bypass) and a write outside the repo (should **fail**, proving strict mode).
- Optional **managed settings (Step 4)** to make the policy un-overridable, and a **token budget** for
unattended runs (see `docs/TOKEN_BUDGET.md`).

Finish with one line: the path written, whether the backend is present, and "restart required".
21 changes: 20 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,23 @@
# Secrets — bot token (GH_BOT_TOKEN) and any local env. Commit .env.example only.
.env
.env.*
!.env.example
!.env.example

# Machine-local hardened profile (bypass + sandbox) — never inherited by a clone.
.claude/settings.local.json

# Personal / editor config that the OS sandbox masks with /dev/null device nodes,
# so it surfaces as bogus "untracked" entries in a sandboxed session (see
# docs/HARDENING.md → Caveats). Never part of the project. NOTE: intentionally does
# NOT include .mcp.json, .gitmodules, or .claude/{hooks,skills,routines,launch.json}
# — those can be legitimate tracked project files; the explicit-staging rule in
# .claude/agents/implementer.md protects them instead.
.bashrc
.bash_profile
.zshrc
.zprofile
.profile
.gitconfig
.ripgreprc
.idea/
.vscode/
29 changes: 29 additions & 0 deletions docs/HARDENING.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,19 @@ command -v cmd.exe # should print nothing
sudo gpasswd -d "$USER" docker # if you don't need Docker in this distro
```

> **Gotcha — cutting `/mnt` breaks any tool pinned to a Windows binary.** WSL+Windows setups often
> point Linux tools at Windows executables under `/mnt/c`. Once `/mnt` is gone those fail with
> `cannot run /mnt/c/.../foo.exe: No such file or directory`. The common one is **git's SSH**: a global
> `core.sshCommand = /mnt/c/Windows/System32/OpenSSH/ssh.exe` (and often a `~/.zshrc`/`~/.bashrc`
> `alias ssh=...exe`) makes `git push` fail. Fix — switch to the Linux toolchain:
> ```bash
> sudo apt-get install -y openssh-client # provides /usr/bin/ssh
> git config --global --unset core.sshCommand # fall back to the Linux ssh on PATH
> # then either add a Linux SSH key to GitHub, or move the remote to https + `gh auth setup-git`
> ```
> Also scrub any `alias ssh=/mnt/...exe` from your shell rc. Same applies to editors, credential
> helpers, or `GIT_*` vars pointed at `.exe` paths.

---

## Step 3 — Activate and verify
Expand Down Expand Up @@ -320,6 +333,22 @@ agent operates *inside*, not one it configures.
- **Worktrees outside the repo** aren't writable under the sandbox. Under strict mode
(`allowUnsandboxedCommands: false`, recommended) such a write *fails* rather than escaping — so keep
worktree paths inside the repo, or allowlist the specific command via `excludedCommands`.
- **In-session commands are sandboxed too — including the `!` prefix.** Anything Claude Code runs,
whether a tool call or a command you type with the `!` prefix, runs inside the Bash sandbox (writes
confined to repo + `$TMPDIR`). Under strict mode that means **host/home config changes fail even when
you type them yourself** — `git config --global` (`~/.gitconfig`), editing `~/.zshrc`, `ssh-keygen`
(`~/.ssh`), `gh auth` (`~/.config/gh`), etc. all error with `Read-only file system`. Run those in a
**real terminal outside Claude Code**. (This is the containment working as intended, not a bug — but
it surprises people the first time.)
- **Sandboxed sessions mask config paths as `/dev/null` — expect phantom `git status` noise.** The
sandbox bind-mounts `/dev/null` over sensitive paths it won't let the agent read (shell rc, `.gitconfig`,
editor dirs, `.mcp.json`, and Claude's own `.claude/{hooks,skills,routines,launch.json}`). In a
sandboxed view these appear as **character-device files** (`ls -l` shows `crw-rw-rw- … 1, 3`), which
`git status` reports as untracked/modified even though they aren't real project files. This is expected,
not corruption. Two consequences: (1) **never `git add -A` / `git commit -a`** — git can't index a
device node and the commit may abort; stage explicit paths instead (the agent instructions enforce this).
(2) The unambiguous personal dotfiles are gitignored so they don't surface; `.mcp.json`/`.gitmodules`/
`.claude/*` are deliberately *not* ignored (they can be real), so rely on explicit staging there.
- **Open PRs gate loop advancement.** A typical loop won't start a new ticket while a PR is open —
that's by design (it keeps you the merge gate). Review/merge to let it advance.
- **The committed `settings.json` is owned by the harness at runtime** (it may rewrite the working-tree
Expand Down
Loading