looper · diff
git:20260712.a05f86a to git:20260717.ebd339a
11 added, 3 removed. Audit B to B.
---
name: looper
description: Use when installing, bootstrapping, configuring, starting, verifying, operating, or troubleshooting Looper, looperd, the looper CLI, ~/.looper config, or runtime paths; when setting up Looper with opencode, claude-code, codex, cursor-cli, or Grok Build; when registering repos or configuring planner/reviewer/fixer/worker loops; or when diagnosing status, logs, osascript, git, gh, LOOPER_TOKEN, writable path, or daemon startup issues.
---
# Looper
Use this skill when an agent needs to install, configure, start, check, operate, or troubleshoot Looper (`looper` CLI, `looperd` daemon, or files under `~/.looper`).
It also covers the full webhook-mode lifecycle — turning it on, installing or validating the `gh webhook` extension, confirming forwarders are healthy, diagnosing a degraded runtime, clearing stale GitHub CLI hooks with `looper webhook cleanup`, and judging when a daemon restart is actually needed.
+ `looperd` also watches its selected config source while running. Curated hot-safe policy changes—including `agent.vendor`—apply to claims made after publication without restarting the daemon; active runs keep the configuration snapshot they started with. The scheduler remains available when the daemon starts without a vendor, so configuring one later can activate an already prepared model/params profile. Leaving one configured vendor—by switching or clearing it—requires empty `agent.params` and, when a model is explicit, a paired model change or unset; cross-vendor continuations keep checkpoint/worktree state but start a fresh native session. Invalid edits and changes to process-owned settings are rejected as a whole while the daemon keeps its last-known-good snapshot. Use the Configuration page at `/dashboard/config` for supported field-level edits and reload diagnostics, and read [`references/config.md`](references/config.md) before deciding that a restart is required.
+
+ Dashboard writes use the revision bound to the published values returned by the config read, then repeat an identity/mode/byte check immediately before atomic rename. This detects external generations present before the final check, including one not yet accepted by the watcher. Portable filesystems leave a tiny final-check-to-rename race, so avoid simultaneous manual and dashboard writes. Without token authentication, config PATCH requires a direct loopback peer and Host authority and rejects proxy-forwarding headers; proxied access requires `local-token` authentication. Dashboard serialization preserves the selected format and unknown top-level extension sections, but may normalize comments and lexical ordering; it also rejects symlinked config paths. Use a targeted manual edit of the symlink target when those constraints matter.
+
**When NOT to use this skill:** developing on the Looper codebase itself (Go sources at `cmd/`, `internal/`, `pkg/`). For that, follow `AGENTS.md` and standard Go tooling.
## Looper in one paragraph
Looper is a local daemon (`looperd`) that polls GitHub and runs four agent loops in their own git worktrees. Each loop is gated by GitHub labels:
| Role | Default discovery | Hands off via |
| --- | --- | --- |
| 🧭 **Planner** | Open issues with `looper:plan`, assigned to current user | Opens spec PR labeled `looper:spec-reviewing` |
| 🔍 **Reviewer** | PRs where current user is review-requested, plus `looper:spec-reviewing` follow-up | A clean review on a `looper:spec-reviewing` PR promotes it to `looper:spec-ready` |
| 🔧 **Fixer** | Open non-draft PRs authored by current user with actionable threads | Pushes fixes; reviewer re-runs |
| 🚢 **Worker** | Open issues with `looper:worker-ready` (assigned), or PRs labeled `looper:spec-ready` | Implements on the same PR until checks pass |
All trigger fields combine with logical AND; empty label lists mean "no label constraint." Triggers are customizable per role and per project — see [`references/config.md`](references/config.md) for the full schema, validation rules, and override examples.
Manual loop starts always work, even when `roles.<role>.autoDiscovery=false`:
```bash
looper plan --project <id> --issue <num>
looper review <owner>/<repo>#<pr> [--loop]
looper work --project <id> --issue <num>
looper loop start --type fixer --pr <owner>/<repo>#<pr>
```
## One-shot install and configuration
Use this when the user wants Looper installed, configured, and running end-to-end. Confirm each destructive step before running it (config writes, daemon start, project add).
### Step 0 — Preflight (read-only)
Looper currently supports macOS (`darwin-arm64`) and Linux (`linux-amd64`). Stop and ask the user how to proceed if the host is not supported:
```bash
case "$(uname -s)-$(uname -m)" in
Darwin-arm64|Darwin-aarch64) ;;
Linux-x86_64|Linux-amd64) ;;
*) echo "Looper supports macOS arm64 and Linux x64 only; stop and confirm with the user before continuing." >&2 ;;
esac
```
On Linux, use detached foreground-mode daemon management; launchd supervision is macOS-only.
```bash
case "$(uname -s)" in
Darwin) ;;
*) echo "Skip macOS launchd assumptions on this host." >&2 ;;
esac
```
Then check required tools:
```bash
command -v git
command -v gh
gh auth status
command -v osascript # required if osascript notifications stay enabled
```
If `git` or `gh` are missing, ask the user before installing them. On macOS with Homebrew:
```bash
brew install git gh
```
If `gh auth status` is not authenticated, ask the user to run `gh auth login`.
For deeper preflight detail, see [`references/daemon.md`](references/daemon.md).
### Step 1 — Detect available agent vendors
Auto-detect installed agent CLIs in parallel before asking:
| `agent.vendor` | Detect with |
| --- | --- |
| `claude-code` | `command -v claude` |
| `codex` | `command -v codex` |
| `opencode` | `command -v opencode` |
| `cursor-cli` | `command -v agent` |
| `grok-build` | `command -v grok` |
Use the `question` tool to let the user pick one. List detected vendors first, marked `(installed)`, with undetected ones appended as `(not installed — needs setup)`. If multiple are installed, do not impose an opinionated default — present them in detection order and let the user choose.
If none are installed, ask the user which one they want to install before continuing; do not proceed to bootstrap with a vendor whose CLI is missing.
After the user picks a vendor, verify it is authenticated (run the vendor's own status command, e.g. `claude --version` followed by a quick auth check, or `agent status`). If the vendor CLI exits with an auth error, surface it and ask the user to log in via the vendor's own flow before continuing.
- Looper inherits the vendor's own authentication (e.g. `claude login`, `agent login`, or env vars in the user's shell). For xAI Grok Build (`agent.vendor = "grok-build"`, executable `grok`), use `grok login --device-auth` or provide `XAI_API_KEY` in the daemon environment. **Do not** store agent credentials or API-key values in the Looper config file (commonly `~/.looper/config.toml`, with some existing installs still using `~/.looper/config.json`).
+ Looper inherits the vendor's own authentication (e.g. `claude login`, `agent login`, or env vars in the user's shell). For xAI Grok Build (`agent.vendor = "grok-build"`, executable `grok`), use `grok login --device-auth` or provide `XAI_API_KEY` in the daemon environment. Prefer those vendor/daemon authentication mechanisms over storing credentials in Looper configuration. If the user explicitly manages a value through `agent.env`, treat it as a local secret: the dashboard/API returns only its key, never its value, and it must not be committed or copied into examples.
Grok Build fresh runs default to `--always-approve` and `--sandbox off` so the agent can update Git metadata outside a linked worktree. Configured arguments override defaults: operators can select a stricter sandbox when the repository layout permits it; `--permission-mode` may prompt or fail unattended runs; non-`plain` output can prevent direct completion-marker parsing; and `-p`/`--single` replaces Looper's generated task prompt. Daemon native resume and interactive `looper resume` takeover are unsupported for Grok Build; retries use a fresh checkpoint prompt, and Looper never uses ambient `--continue`.
### Step 2 — Pick the first project to watch
Use the `question` tool with exactly these three options:
1. **Use the current directory** — "Register the repo at the current working directory (must be a git checkout)."
2. **Enter a project path** — "Provide an absolute path to a local git repository on disk."
3. **Skip for now** — "Bootstrap Looper without a project; add one later with `looper project add`."
Resolution rules per choice — bind the user-provided path to a shell variable (e.g. `REPO=...`) so it does not collide with `$PATH`:
- **Current directory**: `REPO="$(git -C "$PWD" rev-parse --show-toplevel)"`. If `git` errors, the directory is not a git repo — fall back to asking for an explicit path.
- **Project path**: validate the path is absolute and contains a `.git` entry: `test -d "$REPO/.git" || test -f "$REPO/.git"` (the file form supports git worktrees). Reject relative paths and ask again.
- **Skip for now**: continue to Step 3 with no `--project-path` flag.
Save the resolved absolute path (if any) for Step 4. See [`references/cli.md`](references/cli.md) for `looper project add` semantics.
### Step 3 — Install the `looper` CLI
```bash
curl -fsSL https://raw.githubusercontent.com/nexu-io/looper/main/scripts/install.sh | sh
looper --version
```
If `looper --version` fails, do not guess a new install location. The installer controls placement; the typical fix is a `PATH` problem in the user's shell. Determine where `install.sh` placed the binary, then ask the user whether to add that directory to their shell's `PATH` (e.g. by editing `~/.zshrc`).
### Step 4 — Bootstrap config, daemon, and first project
`looper bootstrap` writes the active Looper config file (usually `~/.looper/config.toml`; some existing installs still use `~/.looper/config.json`), installs the managed daemon to `~/.looper/bin/looperd`, optionally registers a project, and starts `looperd`.
**If a Looper config file already exists (commonly `~/.looper/config.toml` or legacy `~/.looper/config.json`), do NOT pass `--yes`.** Inspect first with `looper config show`, then triage by what is missing or wrong:
| Existing-config state | Action |
| --- | --- |
| Config exists, daemon healthy, no projects yet | Run `looper project add` for the chosen path; skip bootstrap |
- | Config exists with wrong/missing `agent.vendor` | Targeted edit to `agent.vendor` after confirming with the user |
+ | Config exists with wrong/missing `agent.vendor` | Targeted edit after confirmation; leaving a configured vendor (switch or clear) requires empty `agent.params` and a paired explicit model change/unset |
| Config exists, daemon unhealthy | Triage with `looper daemon status` and `looper daemon logs --startup` first; do not re-bootstrap blindly |
| Config exists and is correct | Skip bootstrap; go to Step 5 verification |
When the config does not yet exist and you have the user's selections from Steps 1–2:
```bash
# With a project path from Step 2
looper bootstrap --yes \
--project-path "$REPO" \
--agent-vendor "<selected-vendor>"
# Skipped project in Step 2
looper bootstrap --yes \
--agent-vendor "<selected-vendor>"
```
If the user prefers to drive bootstrap themselves: `looper bootstrap` (interactive). See [`references/cli.md`](references/cli.md) for every supported flag.
#### Plane task-source + Feishu HITL variant
Use this when the issues live in a [Plane](https://plane.so) project (not GitHub) but the code + PRs stay on GitHub. Looper reads work-items from Plane and opens PRs on the GitHub `repo`. `--provider plane` generates a **fresh** config, so only use it when no config exists yet.
```bash
looper bootstrap --yes \
--provider plane \
--project-path "$REPO" \
--code-repo <owner>/<repo> \
--plane-workspace <workspace-slug> \
--plane-project <plane-project-uuid> \
--trigger-label looper:plan \
--feishu-webhook-env LOOPER_FEISHU_WEBHOOK_URL \
--agent-vendor "<selected-vendor>"
```
`--code-repo` may be omitted if `$REPO` has a `github.com` origin (it is auto-detected). Two env vars must be exported in the daemon's shell before Step 5 (never store them in the config):
```bash
export PLANE_API_KEY="<plane-api-key>" # matches --plane-token-env (default PLANE_API_KEY)
export LOOPER_FEISHU_WEBHOOK_URL="<feishu-bot-webhook-url>" # matches --feishu-webhook-env
```
Full flag reference, config shape, and follow-ups: [`references/plane.md`](references/plane.md) and [`docs/plane-provider.md`](../../docs/plane-provider.md).
### Step 5 — Verify the install
Run all of these and report the results. Do not restart the daemon if status is healthy.
```bash
looper status
looper daemon status
looper daemon logs --startup
looper config show
looper project list
```
The bundled diagnostic helper is read-only and safe to run; invoke it via its absolute skill path (it is not on `PATH`):
```bash
bash <skill-bundle>/scripts/check.sh
```
Replace `<skill-bundle>` with the actual install location of this skill (commonly under `~/.claude/skills/looper/` or wherever the skill installer placed it). If the path is unknown, skip the helper and rely on the `looper`/`gh` checks above.
A healthy install shows:
- `looper status` reports daemon running and config valid.
- `looper daemon status` shows a PID, recent start time, no `last error`.
- `looper config show` lists the expected `agent.vendor` and projects.
- `looper project list` lists every repo the user expects.
- `gh auth status` is authenticated for those repos.
If `server.authMode` is `local-token`, the user needs to export the token in their shell before running CLI commands:
```bash
export LOOPER_TOKEN="<value-of-server.localToken>"
```
For daemon log layout and supervised vs detached mode, see [`references/daemon.md`](references/daemon.md).
### Step 6 — Add additional projects (optional)
```bash
looper project add /absolute/path/to/repo --id <stable-id> --repo <owner>/<repo>
```
Always prefer absolute paths and confirm the GitHub slug (`owner/repo`) before running.
### Step 7 — First loop (smoke test)
Suggest a smoke test only after explicit confirmation, since this triggers automation against the user's GitHub repo. **Pick a non-production repo and a low-risk issue the user is happy to plan.** Do not run smoke tests against critical production workflows.
```bash
looper plan --project <id> --issue <num>
looper ps
looper logs <id> --follow
```
### Common install failures
| Symptom | Likely cause | Fix |
| --- | --- | --- |
| `tools.gitPath` or `tools.ghPath` could not be resolved | `looperd` cannot find binaries in its env | Set explicit `tools.gitPath` / `tools.ghPath` in config |
| `tools.osascriptPath is required when osascript notifications are enabled` | macOS notifications enabled but `osascript` not resolvable | Set `tools.osascriptPath`, or disable `notifications.osascript.enabled` after confirming |
| `authMode=local-token requires server.localToken` | Token mode without token | Add `server.localToken` and export `LOOPER_TOKEN` for the CLI |
| `agent.vendor` missing | No agent configured | Set `agent.vendor` to a supported vendor whose CLI is installed locally |
| Runtime path not writable | `~/.looper/`, `logs/`, `backups/`, or worktree root not writable | Fix ownership/permissions, do not delete data without explicit confirmation |
| Daemon binary missing | `~/.looper/bin/looperd` not installed | `looper daemon install --force`, then `looper daemon start` |
| `looper --version` not found | Installer placed binary outside `PATH` | Identify install dir, ask user to add it to shell `PATH` |
## References
For deeper detail, consult these bundled docs before acting:
- [`references/cli.md`](references/cli.md) — installed `looper` CLI commands, install/uninstall scripts, `looper bootstrap`, `looper project add`, daemon lifecycle, loop inspection.
- [`references/config.md`](references/config.md) — full Looper config shape, every field, validation rules, env var overrides, CLI flag overrides, role trigger customization, reviewer event mapping.
- [`references/daemon.md`](references/daemon.md) — `looperd` startup, supervised vs detached mode, launchd integration, log locations, startup-failure triage.
- [`references/plane.md`](references/plane.md) — Plane task-source provider + Feishu HITL: extra bootstrap flags, the two env vars, generated config shape, and a discovery verify step.
- [`scripts/check.sh`](scripts/check.sh) — read-only local diagnostic. Verifies `git`, `gh`, `gh auth status`, optional `osascript`, `looper --version`, config presence, and `~/.looper` writability. Invoke via absolute skill path.
When in doubt, prefer read-only checks first:
```bash
looper status
looper daemon status --json
looper daemon logs --startup
looper config show
looper webhook status
```
When webhook mode is degraded, inspect stale GitHub CLI forwarder hooks before restarting the daemon:
```bash
looper webhook cleanup owner/repo
```
Only run deletion after the dry run shows stale `cli` hooks and the user confirms:
```bash
looper webhook cleanup owner/repo --confirm
```
## Safety rules
- Do not overwrite or rewrite the Looper config file (`~/.looper/config.toml` on new installs; `~/.looper/config.json` on some existing installs) without explicit user confirmation. Prefer targeted edits.
+ - Do not restart `looperd` merely to apply a hot-safe config change. Check `/dashboard/config` or daemon logs for the reload result; restart only for an explicitly reported restart-bound change or a separate lifecycle reason.
+ - Do not assume the dashboard is a lossless text editor. It preserves format and unknown top-level sections, but may normalize comments, quoting, and key/table order; it refuses to replace a symlinked config path.
- Do not delete runtime artifacts (`~/.looper/looper.sqlite`, `backups/`, `logs/`, `worktrees/`) unless the user explicitly asks and understands the impact.
- Starting or restarting `looperd` can launch background automation against configured GitHub repositories — confirm intent before doing so.
- Do not toggle `daemon.mode` (foreground ↔ launchd) without confirming; supervised mode persists across login/reboot.
- Reviewer defaults are intentionally action-taking (`clean=APPROVE`, `blocking=REQUEST_CHANGES`) while `enableSelfReview` stays off. Do not broaden reviewer authority further without explicit user opt-in (for example enabling self-review or auto-merge, or relaxing review-event guardrails).
- Do not overwrite or delete existing `looper:*` labels in user repos without confirmation; they may have local customizations.
- Never print secrets from config or environment. Redact tokens and API keys as `***` in summaries.
- Prefer `looper daemon status`, `looper daemon logs`, and the read-only checks above before making changes.
## Common mistakes
- `cat ~/.looper/config.*` as a first move: use `looper config show` instead and redact secrets.
- - Restarting the daemon under pressure: check status/logs first; restart can re-trigger automation.
+ - Restarting after every config edit: hot-safe policy reloads automatically for claims made after publication. Check the Configuration page or logs first; restart can re-trigger automation.
+ - Assuming every valid edit was applied: a restart-bound field in the same candidate rejects the entire reload, and the last-known-good snapshot remains active until the file is corrected or the daemon is restarted with that config.
+ - Submitting a stale Configuration page repeatedly: the patch revision intentionally conflicts after another editor changes the file; wait for a safe edit to publish and refresh, or resolve the displayed reload diagnostics in the file before retrying.
- Disabling `notifications.osascript.enabled` silently: confirm the change or set an explicit `tools.osascriptPath`.
- Rewriting the whole config for one fix: make targeted edits and preserve existing settings.
- Treating a missing `~/.looper/` directory as permission to create or delete data: explain impact and ask first.
- Running smoke tests against production repos: pick low-risk issues only.