| 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, Grok Build, Pi, or Oh My Pi (omp); 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 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 for the full schema, validation rules, and override examples.
Manual loop starts always work, even when roles.<role>.autoDiscovery=false:
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:
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.
case "$(uname -s)" in
Darwin) ;;
*) echo "Skip macOS launchd assumptions on this host." >&2 ;;
esac
Then check required tools:
command -v git
command -v gh
gh auth status
command -v osascript
If git or gh are missing, ask the user before installing them. On macOS with Homebrew:
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.
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 |
pi | command -v pi |
omp | command -v omp |
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. For Pi (agent.vendor = "pi", executable pi) and Oh My Pi (agent.vendor = "omp", executable omp), use each vendor's own login/config. 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.
Pi fresh runs default to -p <prompt> --approve (project-local .pi trusted for the run). Oh My Pi (omp) fresh runs default to -p <prompt> --cwd <worktree> --auto-approve. Configured print/approve/cwd/approval-mode flags override those defaults. Daemon native resume and interactive looper resume takeover are unsupported for pi and omp; retries use a fresh checkpoint prompt.
Step 2 — Pick the first project to watch
Use the question tool with exactly these three options:
- Use the current directory — "Register the repo at the current working directory (must be a git checkout)."
- Enter a project path — "Provide an absolute path to a local git repository on disk."
- 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 for looper project add semantics.
Step 3 — Install the looper CLI
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 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:
looper bootstrap --yes \
--project-path "$REPO" \
--agent-vendor "<selected-vendor>"
looper bootstrap --yes \
--agent-vendor "<selected-vendor>"
If the user prefers to drive bootstrap themselves: looper bootstrap (interactive). See references/cli.md for every supported flag.
Plane task-source + Feishu HITL variant
Use this when the issues live in a Plane 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.
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):
export PLANE_API_KEY="<plane-api-key>"
export LOOPER_FEISHU_WEBHOOK_URL="<feishu-bot-webhook-url>"
Full flag reference, config shape, and follow-ups: references/plane.md and 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.
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 <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:
export LOOPER_TOKEN="<value-of-server.localToken>"
For daemon log layout and supervised vs detached mode, see references/daemon.md.
Step 6 — Add additional projects (optional)
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.
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 — installed looper CLI commands, install/uninstall scripts, looper bootstrap, looper project add, daemon lifecycle, loop inspection.
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 — looperd startup, supervised vs detached mode, launchd integration, log locations, startup-failure triage.
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 — 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:
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:
looper webhook cleanup owner/repo
Only run deletion after the dry run shows stale cli hooks and the user confirms:
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 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.