From 97aabd3d7573409caec521c0a07eaef4ecd92c06 Mon Sep 17 00:00:00 2001 From: didericis Date: Fri, 8 May 2026 14:58:46 -0400 Subject: [PATCH] docs: trim CLAUDE.md to minimal orientation Drop the Intended design section and PRD references; keep only What this is, Goals, Non-goals, Repository layout, Conventions, and When you're unsure. Co-Authored-By: Claude Opus 4.7 --- CLAUDE.md | 150 ------------------------------------------------------ 1 file changed, 150 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8baa7ab..06243e5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,14 +34,6 @@ and env vars into it. - `docs/research/` — research notes (empty for now, kept tracked via `.gitkeep`). - `.claude/skills/init-entry/` — project-local Claude Code skill providing `/init-entry` for adding journal entries. Snapshotted from `~/.claude/skills/init-entry/` at scaffold time; refresh deliberately if it drifts. -The container launcher scripts (`Dockerfile`, `cli.sh`, -`lib/*.sh`) landed in PRD 0001 and were -extended in PRD 0002 with `lib/manifest.sh`, -`lib/env_resolve.sh`, and `lib/skills.sh`. Note: any -future repo-root `skills//` directory (skills sent into the -container) is a distinct concept from `.claude/skills//` (Claude -Code skills used while working in this repo) — don't conflate them. - ## Conventions - Text-driven content. `docs/JOURNAL.md` is an append-only stream of thought, @@ -57,148 +49,6 @@ Code skills used while working in this repo) — don't conflate them. A `commit-msg` hook in `.githooks/` enforces this. Activate it once per clone with `git config core.hooksPath .githooks`. -## Intended design - -PRD 0002 lands the manifest-driven agent flow described below. The -`defaults/` directory and the repo-side `skills/` snapshot/diff loop -sketched at scaffold time are deferred — see "Deferred from the -scaffold sketch" at the end of this section. - -### Manifest - -Per-agent configuration lives in `claude-bottle.json` under an `"agents"` key. -`cli.sh` looks for this file in two locations and merges them: - -1. **Current working directory** (`$PWD/claude-bottle.json`) — project-local agents. -2. **Home directory** (`$HOME/claude-bottle.json`) — personal global agents. - -If both exist, the two `agents` objects are merged (home is the base, cwd -entries win on a same-agent-name conflict). -If neither file exists, `cli.sh` dies with a clear message. - -Each agent has three attributes: - -- `env` — hash of env vars. Each value is a JSON string whose mode - is selected by sentinel prefix: - - `"?"` — value is prompted at runtime from `/dev/tty` - (silent), exported into the launcher process, and forwarded to - the container via `docker run -e NAME` (no `=value`). Never - written to disk, never on argv. The launcher always asks, even - if a same-named var is already in the parent shell. `` - is rendered verbatim as the prompt body; the launcher appends - ` (input hidden): `. Bare `"?"` is allowed and falls back to a - default `claude-bottle: secret value for NAME` prompt. - - `"${HOST_VAR}"` — exact `${IDENT}` form, where `IDENT` matches - `[A-Za-z_][A-Za-z0-9_]*`. Value is read from `$HOST_VAR` in the - host process env at launch time. Treated the same as a secret on - the wire: copied into this process under the target name, - forwarded as `-e NAME` (no `=value`), never written to disk. - - any other string — literal value, hardcoded in the manifest. - Written to a mode-600 env-file under `mktemp -d` and passed to - docker via `--env-file`. Newlines are rejected up front because - docker `--env-file` cannot represent them. A literal whose text - starts with `?` or matches `${IDENT}` is not representable in - v1 — pick a different value or revisit the convention. -- `skills` — list of skill names. Each is `docker cp`'d from - `~/.claude/skills//` into the running container's - `~/.claude/skills//`, preserving per-skill directory structure - (no flattening, no archives). If a referenced skill is missing on - the host, `cli.sh` fails with a clear message naming the skill - and the path checked. The host→repo fallback and host↔repo diff - prompt described in the original sketch are deferred. -- `prompt` — string prepended to the chat when the container session - boots. Delivered by writing the string to a file inside the - container via `docker cp` (so the prompt content does not land on - `docker exec` argv) and passing it to - `claude --append-system-prompt-file `. Note: as of the - claude-code version pinned in the Dockerfile, this flag is real but - is not surfaced in the alphabetized `claude --help` output (only - mentioned obliquely under `--bare`); a future rename or removal will - break the launcher with a clear error from claude itself. Bare - `start` (no ``) is intentionally not supported — `` - remains required. -- `ssh` — optional array of SSH host entries. Each entry is an object - with five required keys: - - `Host` — the `Host` alias written to `~/.ssh/config` in the - container (also the name you use as the ssh destination). - - `IdentityFile` — absolute path to the private key file on the host - (leading `~` is expanded). At launch the key is `docker cp`'d into - `/root/.claude-bottle-keys/` (mode 700, root-owned), loaded into a - root-owned `ssh-agent` listening on `/run/claude-bottle-agent.sock`, - and the key file is then deleted. The agent socket is `chmod 666` - so the `node` user can connect; the agent protocol only exposes - signing operations, never the key bytes. Keys must be - passphrase-less (no TTY for `ssh-add` to prompt against). - - `Hostname` — the actual hostname or IP for `HostName`. - - `User` — the SSH username for `User`. - - `Port` — the SSH port number for `Port`. - - `KnownHostKey` — (optional) the host's public key, written to - `~/.ssh/known_hosts` under both the `Host` alias and the - `Hostname` (so the lookup succeeds whether the connection uses - the alias or the raw IP/host, e.g. a git remote URL with the - bare IP). Eliminates the interactive host-verification prompt on - first connect. - - Per-Host blocks in `~/.ssh/config` use `IdentityAgent - /run/claude-bottle-agent-public.sock` rather than `IdentityFile`, so SSH - always reaches the agent regardless of `SSH_AUTH_SOCK`. The public - socket is served by a root-owned `socat` forwarder, not by the agent - itself: OpenSSH's `ssh-agent` enforces a `SO_PEERCRED`-based UID-match - check on every connection (only accepts peers with euid 0 or matching - the agent's own uid), so non-root callers like `node` are rejected - even when the socket is mode 666. `socat` runs as root, accepts node's - connections on the public socket, and proxies to the real agent socket - at `/run/claude-bottle-agent.sock`; from the agent's perspective the peer - is uid 0 and passes the check. - - Why an in-container agent (not a bind-mounted host agent): Docker - Desktop on macOS does not forward Unix-domain socket `connect()` - across the macOS↔Linux VM boundary (returns `ENOTSUP`). Running the - agent inside the container sidesteps that while preserving the - isolation property we want (node can use the key for SSH but cannot - read the bytes — root-owned agent and forwarder, no `CAP_SYS_PTRACE`). - - `cli.sh start` validates that every key file exists on the host - before the y/N prompt, then after the container is running it spawns - the in-container `ssh-agent`, loads the keys, deletes the key files, - and writes `~/.ssh/config` (mode 600) with one `Host` block per - entry. - -Agent keys (the top-level keys of `claude-bottle.json`) should already be -slug-friendly (lowercase, alphanumeric + hyphens). The container name -is `claude-bottle-`, with a numeric suffix appended on conflict — -so two parallel starts of the same agent get distinct containers -(`claude-bottle-journal`, `claude-bottle-journal-2`, ...) instead of the -second failing. Two distinct agent keys that slug to the same value -(e.g. `"Review PR"` and `"review-pr"`) will both work but become hard -to tell apart in `docker ps`; pick keys that are already slugs to -avoid that ambiguity. `CLAUDE_BOTTLE_CONTAINER` still pins an exact name -and keeps the strict-conflict failure if it's already taken. - -### Confirmation - -Before launching a container, `cli.sh` shows the resolved plan and -waits for a single `y/N`: - -- agent name, image, container name -- env var names (never values; secrets are also never identified - separately, since the name itself plus the manifest is the source - of truth) -- skill names being sent -- prompt length and first line only - -Pass `--dry-run` (or set `CLAUDE_BOTTLE_DRY_RUN=1`) to print the plan -and exit before any `docker run` / `docker cp` / `docker exec`. - -### Deferred from the scaffold sketch - -The pre-PRD-0002 sketch described a repo-root `skills//` -snapshot, a `defaults/secrets.json` + `defaults/config.env`, and a -host↔repo skill diff loop. None of that is implemented; v1 reads the -manifest, prompts secrets, forwards literals via `--env-file`, and -copies host skills directly into the container. Reopen the question -in the journal if and when the snapshot story matters. - ## When you're unsure Ask. Default to drafting in chat over editing files when the request is ambiguous.