diff --git a/CLAUDE.md b/CLAUDE.md index 714a88d..9e15f8b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -69,11 +69,17 @@ Hard-won lessons that apply across every project: - **Read the gotcha before you diagnose.** When an issue surfaces (HTTP 4xx/5xx, hung connection, perpetual `OutOfSync`, "permission denied", silent drop, etc.), the FIRST step is to grep `~/dev/claude/projects/best-practices/.md` and the project's `memory/gotchas-*.md` for keywords matching the symptom AND the technologies involved. Search both the *symptom* (e.g., `permission denied`, `30s timeout`, `not matching`) and the *infrastructure* (e.g., `cilium`, `ipBlock`, `ESO`, `apiserver`). Do this BEFORE forming your first hypothesis. Turns a multi-hour investigation into a 5-minute lookup. If the gotcha isn't there, capture the new one when you solve it. - **Validate locally, deploy once.** Don't use the live environment as a test bed. Catch errors with local validation tools before pushing (`helm template`, `kustomize build`, lint/typecheck, `docker inspect`, etc.). Batch fixes locally, push once. -- **Check before you act.** Before writing firewall/network rules, check actual routing (`ip route get`). Before running config management with variables, ensure values are real, not placeholders. Before assuming a container has a shell, `docker inspect` it. +- **Check before you act.** Before writing firewall/network rules, check actual routing (`ip route get`). Before running config management with variables, ensure values are real, not placeholders. Before assuming a container has a shell, `docker inspect` it. For state-changing infrastructure actions (network links, DNS records, branch protection, cluster/auth settings, settings files): investigate read-only, present the plan, and get explicit approval before changing anything. - **Test the full chain immediately.** After wiring up a new service or endpoint, test end-to-end from the user's perspective right away. Don't assume intermediate steps working means the whole chain works. - **Verify scripts should be environment-resilient.** Avoid needing sudo or special access. Test from the accessible side of a connection. Use `curl --resolve` to bypass DNS/proxy layers when testing direct connectivity. - **Automate repeated sequences.** If you run the same 3+ commands in sequence more than once, it should become a script. - **Reflect after milestones.** Don't just finish — review what happened, what went wrong, what can be improved. Write it down. +- **Pre-flight before push.** Before any `git push`: derive the SSH alias from `git remote get-url origin`, confirm that key is loaded (`ssh-add -l`). Passphrase-protected keys must be `ssh-add`ed by the user — hand off, don't retry. If a push fails: commit locally, say so explicitly, note the recovery step in CONTEXT.md. Details: `claude/source-control.md`. +- **Everyday shell/edit footguns.** `grep` exits 1 on no match (breaks `set -e` chains); Edit `replace_all` matches substrings — make the old string unique. Full list: `claude/scripting-conventions.md`. + +## Session End + +Run `/end-session` (session log + CONTEXT.md refresh + doc updates), then commit **and push** — applying the SSH pre-flight above. A session is not finished while completed work sits unpushed. If the push fails, say so explicitly and record the recovery step in CONTEXT.md before stopping. ## Version & Secrets Management (compact rules) @@ -88,13 +94,14 @@ Pull one of these in only when the situation calls for it. Listed in roughly des |---|---| | [`claude/source-control.md`](./claude/source-control.md) | Touching git, creating repos, setting up remotes | | [`claude/agent-runtimes-cp.md`](./claude/agent-runtimes-cp.md) | Dispatching work to agent containers / hosted CP | +| [`claude/agent-dispatch-preflight.md`](./claude/agent-dispatch-preflight.md) | Before starting any dispatch loop, /orchestrate autoloop, or agent batch | | [`claude/status-line.md`](./claude/status-line.md) | Setting or updating the status line topic | | [`claude/plan-mode.md`](./claude/plan-mode.md) | In plan mode — before calling `ExitPlanMode` | | [`claude/spec-driven-development.md`](./claude/spec-driven-development.md) | Starting a milestone / writing or editing specs | | [`claude/milestones.md`](./claude/milestones.md) | Completing a milestone (verification, reflection) | | [`claude/documentation-standards.md`](./claude/documentation-standards.md) | Creating or restructuring project doc files (ABOUT/CLAUDE/MEMORY/CONTEXT/FUTURE/README) | | [`claude/new-projects.md`](./claude/new-projects.md) | Creating a new project from scratch | -| [`claude/scripting-conventions.md`](./claude/scripting-conventions.md) | Writing a new script | +| [`claude/scripting-conventions.md`](./claude/scripting-conventions.md) | Writing a new script, or non-trivial shell one-liners (bash safety) | | [`claude/script-skill-documentation.md`](./claude/script-skill-documentation.md) | Authoring or editing a script in claude-foundations or a skill in custom-claude-skills | | [`claude/knowledge-distillation.md`](./claude/knowledge-distillation.md) | Running `/log`, `/reflect-logs`, or `/distill-best-practices` | | [`claude/ask-minimax-usage.md`](./claude/ask-minimax-usage.md) | Considering delegating a file-heavy task to MiniMax | diff --git a/claude/agent-dispatch-preflight.md b/claude/agent-dispatch-preflight.md new file mode 100644 index 0000000..605a9b6 --- /dev/null +++ b/claude/agent-dispatch-preflight.md @@ -0,0 +1,32 @@ +# Agent Dispatch Pre-flight + +## When this applies + +Before starting `/loop ... /orchestrate`, `/dispatch`, or any unattended or overnight agent batch. Run this checklist once, before the loop starts — not mid-run. + +## Checklist — all must pass before the loop starts + +1. **CP reachable.** `curl -fsS "$CP_URL/health"` returns 200. `CP_URL` is defined in `claude/agent-runtimes-cp.md` — read that file for the correct value and any auth headers required. + +2. **A dispatcher is polling.** Check the CP dispatchers endpoint (see `agent-runtimes-cp.md`). A queued task with no live dispatcher sits forever — confirm at least one dispatcher is active before queuing work. + +3. **Scaffolding present.** + - `.agent-tasks.json` exists and passes `jq empty .agent-tasks.json` (valid JSON). + - All templates, repos, and input files referenced by tasks are resolvable from this host. + - Agent push keys are loaded (`ssh-add -l`) — container agents push branches back; a missing key silently fails the push. + +4. **Auth done this session.** `CLAUDE_CODE_OAUTH_TOKEN` is set in the environment or readable from `~/dev/claude/secrets/claude/long_lived_oauth_token`. Confirm before launching containers. + +Do not start the loop until all four pass. Surface any failure to the user and wait for remediation. + +## Circuit-breaker + +Track task-state changes across invocations. After **5 consecutive invocations with no state change** (no task moved from pending→running, running→completed/failed, etc.): stop dispatching, print a stuck-queue report listing each task and its current status, and wait for the user. Never run an unattended loop without this guard. + +## Recovery + +If a dispatch batch needs to be aborted mid-run: + +- Drain queued tasks: set pending tasks to `cancelled` in `.agent-tasks.json` before the next invocation so the orchestrator does not launch them. +- Let running containers finish or `docker stop` them explicitly. +- Re-dispatch from a clean state once the root cause is resolved. diff --git a/claude/ask-minimax-usage.md b/claude/ask-minimax-usage.md index 32d067f..19a984e 100644 --- a/claude/ask-minimax-usage.md +++ b/claude/ask-minimax-usage.md @@ -21,3 +21,7 @@ ## How to delegate Pass file *paths*, not file *contents*. If you read the input files yourself before invoking, you have already paid the token cost the skill exists to avoid. + +## Failure handling + +MiniMax intermittently returns `invalid system role` and other transient errors. Retry once. If it fails again, fall back to a Claude subagent with the same brief — Sonnet for file-heavy grunt work, Opus for reasoning-grade consults. Never silently drop the task. Log new error shapes to the project's `memory/gotchas-*.md`. diff --git a/claude/scripting-conventions.md b/claude/scripting-conventions.md index f3ee8be..49a2c7d 100644 --- a/claude/scripting-conventions.md +++ b/claude/scripting-conventions.md @@ -1,5 +1,16 @@ # Scripting Conventions +## Bash safety + +- Open every script with `set -euo pipefail` — fail fast on errors, unset vars, and pipeline failures. +- `grep` exits 1 on no match — under `set -e` use `grep ... || true`; under `pipefail`, beware `grep ... | head` triggering SIGPIPE (use `|| true` on the grep side). +- One stdin per process — never pipe into a command that also reads a heredoc; pick one input source. +- Validate JSON before consuming: `jq empty ` or `python3 -m json.tool `. Especially before dispatch loops that iterate over JSON state. +- Quote all expansions: `"$var"`, `"${array[@]}"`. Bare expansions split on whitespace. +- Edit-tool `replace_all` is a substring match — a short `old_string` like `2` will corrupt `24` → `244`. Make `old_string` unique (add surrounding context) or use individual targeted edits. + +## Conventions + - All scripts live in `scripts/` and run from the repository root - Scripts should be idempotent and safe to re-run - Use colour output for pass/fail indicators in verification scripts diff --git a/claude/source-control.md b/claude/source-control.md index f5eb26c..a1dc9b2 100644 --- a/claude/source-control.md +++ b/claude/source-control.md @@ -15,10 +15,33 @@ Pattern: `gitea.oreillyit.nz-`. - `gitea.oreillyit.nz-homelab` → authenticates as `cluster-administrator` (key: `~/.ssh/gitea-cluster-admin`) - `gitea.oreillyit.nz-ai-enablement` → authenticates as `ai_enablement` (key: `~/.ssh/gitea.ai-enablement`) +- `gitea.oreillyit.nz-accelerators` → (key: `~/.ssh/gitea.accelerators.2026`) Git remote URL format: `git@gitea.oreillyit.nz-:/.git` - Example: `git@gitea.oreillyit.nz-ai-enablement:skynet/custom-claude-skills.git` +## SSH agent pre-flight + +Before the first push of a session: + +1. Derive the alias: `git remote get-url origin` — the host segment is the SSH alias. +2. Confirm the key is loaded: `ssh-add -l` — the relevant key must appear. +3. Confirm auth: `ssh -T git@` — expect a Gitea welcome message. + +Claude cannot answer a passphrase prompt. If the key isn't loaded, ask the user to `ssh-add ~/.ssh/` before proceeding. + +In non-interactive contexts (CI, container agents, unattended loops), use `GIT_SSH_COMMAND='ssh -o BatchMode=yes' git push` so a missing credential fails fast instead of hanging on a passphrase prompt. + +## Push failure recovery + +| Symptom | Likely cause | +|---|---| +| `Permission denied (publickey)` | Key not loaded, or wrong alias in remote URL | +| Push hangs silently | Passphrase prompt in non-interactive context — use `BatchMode=yes` | +| 403 after API-created repo | SSH user not added as collaborator — add via Gitea UI or API | + +Commit locally first, surface the failure explicitly, and never end a session with finished work unpushed and unmentioned. Record the recovery step in CONTEXT.md. + ## Working rules - **Always pull before planning work** — run `git pull --ff-only` when entering a project. Work may have been pushed from another machine or by container agents. If the pull fails (diverged history, uncommitted changes), warn the user before proceeding.