Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

430 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Critters

Critters is a TypeScript daemon that polls issue trackers (Linear, Jira, and GitHub Issues) for labeled issues and runs configurable agentic workflows on them using coding CLIs (Claude Code and/or Codex) — planning, execution, review, or fully custom phases. It runs on Bun and orchestrates everything through tmux panes.

Inspired by Stripe's Minions.

Install

curl -fsSL https://raw.githubusercontent.com/ack-ventures/critters/main/install.sh | bash

This downloads the latest binary, installs it to your PATH, and walks you through initial setup.

Commands

  • critters — start the daemon
  • critters version — show version
  • critters update — check for and apply updates
  • critters stop — stop the daemon gracefully
  • critters init — (re-)configure ~/.critters/
  • critters status — show daemon status (active/queued critters, today's stats)
  • critters retry <ID> — retry a failed critter (reset to Todo)
  • critters logs <ID> — show logs for a critter run (--phase, --follow)
  • critters tail — live-stream output from all active critters (--type)
  • critters history — show past critter runs (--last N, --failed, --type, --json)
  • critters kill <ID> — kill a running critter (--all, --type)
  • critters kickoff — trigger an immediate poll (instead of waiting for the next interval)
  • critters list-types — show configured critter types
  • critters init-repo — scaffold .critters.yaml in the current repo
  • critters prompt-help — launch Claude to help design critter types and prompts
  • critters prompt render <type> <phase> — preview a phase prompt with variables substituted, skills concatenated, and tools resolved (flags: --identifier, --title, --description, --repo, --branch, --work-dir, --json)
  • critters clean — clean up stale work directories (--all, --dry-run, --branches, --panes)
  • critters release-notes — show release notes for recent versions
  • critters validate — validate config file without starting daemon
  • critters help — show usage

Flags

  • --dry-run — poll once, show what would happen, and exit
  • --no-tmux — daemonize to background without tmux (logs to ~/.critters/critters.log, writes PID to ~/.critters/critters.pid)
  • --no-watch — disable config file watching (no hot-reload)
  • --skip-update — skip auto-update check on startup
  • --config PATH — use a custom config file
  • --type NAME — filter dry-run to a specific critter type
  • --json-logs — output structured JSON logs (one object per line)

Self-Hosting

See docs/self-hosting.md for the full deployment guide.

Quick start (binary):

curl -fsSL https://raw.githubusercontent.com/ack-ventures/critters/main/install.sh | bash
critters init
tmux new -s critters
critters

Development quick start

Prerequisites: Bun, Claude Code CLI (authenticated) and/or Codex, gh CLI (authenticated), tmux, jq

# Clone and install
git clone https://github.com/ack-ventures/critters && cd critters
bun install

# Configure
cp .env.example .env
# Edit .env and set LINEAR_API_KEY (for Linear) and/or JIRA_HOST, JIRA_EMAIL, JIRA_API_TOKEN (for Jira)
# and/or GITHUB_TOKEN (for GitHub Issues)
# Optionally set SLACK_WEBHOOK_URL for notifications, or SLACK_BOT_TOKEN + SLACK_CHANNEL for threaded notifications

# Optionally tweak critters.config.yaml

# Run
bun start
# or: bun run src/index.ts

Docker

# Clone and configure
git clone https://github.com/ack-ventures/critters && cd critters
cp .env.example .env
# Edit .env — set ANTHROPIC_API_KEY and LINEAR_API_KEY (and/or JIRA_* vars)

# Start
docker compose up -d

# View logs
docker compose logs -f

# Dashboard at http://localhost:3847

The Docker image includes all runtime dependencies (Claude Code CLI, Codex CLI, gh, git, jq).

Installing additional tools (Node.js, Bun, Python, etc.):

The container doesn't include language runtimes by default. To install tools at startup without rebuilding the image, create a setup script at ~/.critters/setup.sh (automatically mounted into the container):

#!/bin/bash
# ~/.critters/setup.sh — runs before the daemon starts

# Node.js (via nvm)
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh"
nvm install 22

# Bun
curl -fsSL https://bun.sh/install | bash

# Python
apt-get update && apt-get install -y --no-install-recommends python3 python3-pip

The script is source'd so any PATH or env changes persist into the daemon and spawned Claude Code sessions. Override the path with CRITTERS_SETUP_SCRIPT env var.

Auth requirements:

  • CLAUDE_CODE_OAUTH_TOKEN — Claude Max/Team subscription token (run claude setup-token on the host, then add to .env or export in your shell). Alternatively, set ANTHROPIC_API_KEY for direct API access.
  • ~/.codex — Codex CLI auth/config (mounted into the container when using Codex phases)
  • ~/.ssh — SSH keys for git clone (mounted read-only)
  • GITHUB_TOKEN — GitHub CLI auth (run gh auth token on the host, then add to .env or export in your shell)

For the pre-built image, replace build: . with image: ghcr.io/ack-ventures/critters:latest in docker-compose.yaml.

To use the latest released binary instead of building from source, set the build target to prod:

# docker-compose.yaml
services:
  critters:
    build:
      context: .
      target: prod  # downloads released binary instead of building from source

Or build directly: docker build --target prod . (append --build-arg CRITTERS_VERSION=1.6.1 to pin a version).

How it works

  1. Watcher polls your issue tracker (Linear, Jira, and/or GitHub Issues) every 120 seconds for issues with the "Critter" label in "Todo" status.
  2. Spawner shallow-clones the target repo into a temp directory and creates a feature branch.
  3. Phase 1 (Planning): A configured coding CLI instance explores the codebase and writes an implementation plan.
  4. Phase 2 (Execution): A configured coding CLI instance implements the plan, commits, pushes, and opens a draft PR.
  5. Status updates flow back to the issue tracker: Todo → In Progress → In Review (on PR) or Critter Failed (on error).

Critter lifecycle

The built-in critter types form a pipeline:

Todo → In Progress → In Review → review ──┬──► Done (merged)
         (create)                          │
                        ┌──────────────────┘
                        ▼
                   Human Review
                        │
                        └──► fix-review-comments ──► In Review (loops back to review)
  1. create picks up "Todo" issues, plans and implements the work, opens a draft PR, and moves the issue to "In Review".
  2. review picks up "In Review" issues, reviews the PR diff, and either merges it (→ "Done") or requests changes (→ "Human Review").
  3. fix-review-comments (optional, user-configured) picks up "Human Review" issues, reads the review feedback from the PR, addresses it, and moves the issue back to "In Review" — sending it through review again.

Without fix-review-comments, issues that need changes stop at "Human Review" for manual intervention (or critters retry --force).

To enable the loop, add a fix-review-comments type to your config:

critterTypes:
  # ... create and review types ...

  fix-review-comments:
    trigger: { label: "Critter Review", status: "Human Review" }
    repo: { clone: true, branch: true }
    phases:
      - name: fix
        prompt: ~/.critters/prompts/fix-review.md
        model: opus
        maxTurns: 50
        tools: default
    outcomes:
      success: { status: "In Review" }
      failure: { status: "Critter Failed" }
    concurrency: 2
    timeoutMinutes: 20
    enrichment: extractPrUrl
    claimStatus: "In Progress"

The prompt file should instruct Claude to read the PR review feedback (for example with gh pr view --comments plus gh api repos/:owner/:repo/pulls/:number/comments for inline review comments), make the requested fixes, commit, and push. Use builtin:execution prompts as a starting point. claimStatus prevents duplicate dispatch if two poll cycles trigger before the fix completes.

Configuration

Settings live in critters.config.yaml:

Field Default Description
provider "linear" Default issue tracker: "linear", "jira", or "github"
pollIntervalSeconds 120 How often to poll for issues
concurrency 2 Max parallel critters
timeoutMinutes 30 Total timeout per task (both phases)
workDir /tmp/critters-work Temp clone directory
triggerLabel "Critter" Label that triggers pickup
maxPlanningTurns 50 Max turns for planning phase
maxExecutionTurns 75 Max turns for execution phase
defaultAllowedTools see file Tools critters can use
repos {} Project ID → repo URL + extra tools
teamRepos {} Team ID → fallback repo URL
defaultRepo Final fallback repo URL when not in description, project config, or team config
tmuxSession "critters" Name of the tmux session to use
branchPrefix "critter" Prefix for feature branch names (<prefix>/<ID>-<slug>)
planningModel "opus" Model for planning phase
executionModel "opus" Model for execution phase
healthPort 3847 HTTP server port for dashboard and health checks (0 to disable)
dashboardToken Shared secret for dashboard POST endpoints (also reads DASHBOARD_TOKEN env var)
maxLogSizeMb 10 Max log file size in MB before rotation (with --no-tmux)
cleanupIntervalMinutes 60 How often periodic work directory cleanup runs (minutes)
cleanupStaleMinutes timeout + 30 Age threshold (minutes) for considering work directories stale
minDiskSpaceMb 1024 Minimum free disk space (MB) required before cloning a repo
jiraStatusMap {} Map critter status names to Jira workflow status names
github GitHub Issues settings block: repos (list of "owner/repo"), statusField (default "Status"), statusMap, statusTypes — see "GitHub Issues as a ticket source"
hooks {} Shell commands run on lifecycle events (see below)
costAlertThreshold Cost (USD) per task that triggers a Slack alert
costBudget Cost (USD) per task that triggers a kill
reviewTriggerLabel "Critter Review" Label that triggers review pickup
reviewModel "opus" Model for reviews
reviewConcurrency 2 Max parallel review critters
reviewTimeoutMinutes 15 Timeout per review
maxReviewTurns 30 Max turns per review
cli "claude" Default CLI adapter: "claude" or "codex"
phases[].sandbox Optional phase-level sandbox override for adapters that support it, e.g. Codex read-only, workspace-write, or danger-full-access
phases[].permissionMode Optional CLI approval override. Claude forwards this as --permission-mode (default, auto, acceptEdits, bypassPermissions, plan, dontAsk). Codex supports auto (--full-auto), bypassPermissions (--dangerously-bypass-approvals-and-sandbox), default, and dontAsk.
autoRetry Auto-retry config: { maxRetries, baseDelaySeconds, maxDelaySeconds }
circuitBreaker Circuit breaker config: { failureThreshold, maxBackoffMinutes }
mcpConfig Path(s) to MCP config JSON file(s), applied to all critters
strictMcpConfig false When true, passes --strict-mcp-config to prevent inheriting operator's MCP servers
metricsRetentionDays 90 Days to retain metrics data before pruning
autoUpdate Auto-update config: { enabled (default: true), intervalMinutes (default: 1440) }
tunnel Tunnel config: { enabled, auth, domain } for ngrok remote access
linearWebhookSecret Linear webhook signing secret (env: LINEAR_WEBHOOK_SECRET)
jiraWebhookSecret Jira webhook secret (env: JIRA_WEBHOOK_SECRET)
githubWebhookSecret GitHub webhook secret (env: GITHUB_WEBHOOK_SECRET)

Per-repo tool overrides merge with the defaults:

repos:
  "project-uuid":
    url: "[email protected]:org/repo.git"
    extraAllowedTools:
      - "Bash(python:*)"
      - "Bash(pip:*)"

You can choose the CLI globally, per critter type, or per phase:

cli: claude

critterTypes:
  create:
    phases:
      - name: planning
        prompt: builtin:planning
        cli: codex
        model: gpt-5.2
        maxTurns: 50
        tools: readonly
      - name: execution
        prompt: builtin:execution
        cli: claude
        model: opus
        maxTurns: 75
        tools: default

Critters preserves the existing tools config for both adapters. Claude enforces the allowlist directly; Codex uses sandboxing plus prompt-level restrictions, so enforcement is capability-based rather than identical.

For Codex phases that need outbound GitHub access through gh, set a phase sandbox explicitly. In practice, PR review and review-fixing phases often need danger-full-access, because Codex workspace-write can block the GitHub API calls that gh relies on.

Phases can also set a permissionMode to control CLI approval behavior. For Claude phases, auto is forwarded directly as --permission-mode auto:

critterTypes:
  create:
    phases:
      - name: execution
        prompt: builtin:execution
        model: opus
        maxTurns: 75
        tools: default
        permissionMode: auto

This lets Claude's auto-classifier approve reasonable tool calls outside the explicit allowlist.

For Codex phases, permissionMode: auto emits Codex's native --full-auto flag while still preserving the configured sandbox:

critterTypes:
  create:
    phases:
      - name: planning
        prompt: builtin:planning
        cli: codex
        model: gpt-5.5
        maxTurns: 50
        tools: readonly
        permissionMode: auto

If a daemon runs inside an external container or VM sandbox where Codex's own sandbox cannot create namespaces, set permissionMode: bypassPermissions on that Codex phase. Critters emits --dangerously-bypass-approvals-and-sandbox for that mode, so only use it when the surrounding runtime already provides the execution boundary.

Hooks

Shell commands that run on lifecycle events. Environment variables available: CRITTER_ISSUE_ID, CRITTER_IDENTIFIER, CRITTER_TITLE, CRITTER_REPO_URL, CRITTER_BRANCH, CRITTER_PR_URL (when applicable).

hooks:
  onTaskStarted: "echo 'Task started'"
  onPrCreated: "curl -X POST https://example.com/notify"
  onMerged: "./scripts/post-merge.sh"
  onTaskFailed: ""
  onReviewStarted: ""
  onNeedsChanges: ""

Web Dashboard

The daemon runs an HTTP server on port 3847 (configurable via healthPort, set to 0 to disable).

Route Method Description
/ or /dashboard GET Live dashboard (Console view) with task stats, charts, and recent activity. Includes a "New Critter" button for creating tickets
/dashboard/inflight GET Card grid of in-flight critters with elapsed/cost/timeout metrics
/dashboard/queue GET Queued critters with next-poll countdown and "Poll now" trigger
/dashboard/history GET Full activity table with type/status/repo filters and search
/dashboard/logs GET Split view: run list on the left, per-run detail (issue info, cost summary, phase timeline, logs) on the right
/dashboard/types GET Configured critter types with phase pipeline visualization and per-type run/success/cost stats
/dashboard/repos GET Configured repos with 14-day runs, success rate, cost, and extra tools
/dashboard/hooks GET Lifecycle hook commands + inbound webhook status + tunnel URL
/dashboard/tokens GET Presence-only listing of known env vars (values are never exposed)
/dashboard/costs GET Spend summary, breakdowns by type and repo, top 10 most expensive runs
/dashboard/models GET Models in use by phase, pulled from critter-type config
/dashboard/releases GET Release notes for recent versions, with the currently running version highlighted
/dashboard/<id> GET Deep link to a single critter's log page
/healthz GET JSON health check (uptime, version, per-type active/queued counts, active critter details, metrics summary)
/metrics GET JSON array of recent metric events
/poll POST Trigger an immediate poll cycle
/review-poll POST Trigger an immediate review poll cycle
/restart POST Restart the daemon process
/stop POST Stop the daemon gracefully
/kill POST Kill active critters by identifier ({ identifiers: string[] })
/api/v1/dashboard GET Dashboard data payload (status, metrics, activity)
/api/v1/issue/<id> GET Per-issue data (phases, cost, PR status) for the log page
/api/v1/metadata GET Provider teams, critter types, and repo info
/api/v1/issues POST Create a critter issue ({ provider, teamId, title, description, critterType })
/api/v1/types GET Configured critter types with per-type 14-day stats
/api/v1/repos GET Configured repos with per-repo 14-day stats
/api/v1/models GET Model usage pulled from phase configs
/api/v1/hooks GET Lifecycle hook commands + webhook + tunnel status
/api/v1/env-status GET Presence-only status of known env vars (no values)
/api/v1/releases GET Bundled release notes JSON
/api/logs/<id> GET Processed log tail for a critter run
/api/logs/<id>/stream GET SSE stream of a critter's live log
/webhook/linear POST Linear webhook endpoint (requires linearWebhookSecret)
/webhook/jira POST Jira webhook endpoint (requires jiraWebhookSecret)
/webhook/github POST GitHub webhook endpoint (requires githubWebhookSecret)

When dashboardToken is configured, POST endpoints require a bearer token. critters status queries the health endpoint to display a quick summary in the terminal.

Webhooks

Webhook endpoints provide near-instant issue pickup instead of waiting for the next poll. Webhooks are additive — polling continues as the fallback. Set LINEAR_WEBHOOK_SECRET, JIRA_WEBHOOK_SECRET, and/or GITHUB_WEBHOOK_SECRET to enable. The daemon must be reachable from the internet — use the tunnel config for ngrok or a reverse proxy.

Creating tickets

Works with both Linear and Jira. For a critter to pick up an issue, it needs:

  • Label: "Critter" (exact match, configurable)
  • Status: "Todo" (Linear), the mapped status via jiraStatusMap (Jira), or the mapped Status field option / status:Todo label (GitHub, see below)
  • Description: must include repo: [email protected]:org/repo.git on its own line (unless a project or team mapping exists in the config)

Optionally, assign the issue to the relevant project and include implementation guidance in the description -- the critter reads it as its task spec.

Custom critter types

Beyond the built-in create and review flows, you can define custom critter types in critters.config.yaml. Each type gets its own trigger label, phase pipeline, model, tools, and outcome statuses.

critterTypes:
  code-audit:
    trigger: { label: "Code Audit", status: "Todo", statusType: "unstarted" }
    repo: { clone: true }
    phases:
      - name: audit
        prompt: ~/.critters/prompts/code-audit.md
        model: sonnet
        maxTurns: 20
        tools: [Read, Glob, Grep, "Bash(git:*)", "Bash(ls:*)"]
    outcomes:
      success: { status: "Done", comment: true }
      failure: { status: "Critter Failed", comment: true }
    concurrency: 3
    timeoutMinutes: 10

Custom types automatically:

  • Prompt Claude to write a .critter-report.md file
  • Upload the report as a .md attachment on the issue
  • Post the report as an inline comment

Prompt files support {{identifier}}, {{title}}, {{description}}, and other variables. See CLAUDE.md for the full reference.

Assignee filtering: Add trigger.assignee to only pick up issues assigned to a specific user. Use an email address (e.g., "[email protected]") or "me" for the authenticated user. Useful in shared projects where you don't want critters picking up every labeled issue.

Quiet mode: Add quietComments: true to suppress status/progress comments (e.g., "picking up task...", "phase completed in..."). Report content and error comments are still posted.

Claim status: Critter types with trigger.statusType: "unstarted" automatically transition issues to "In Progress" when picked up, preventing duplicate dispatch. Override with claimStatus: "Reviewing" (or any status), or set claimStatus: null to disable. Types without statusType: "unstarted" (e.g., review types) don't claim by default.

Model guidance: Use sonnet or opus for custom types. Haiku often ignores tool-use instructions and produces shallow output.

If critterTypes is omitted from config, the daemon synthesizes the default create and review types from the flat config fields — fully backward compatible.

Use cases

Critters is a general-purpose agent orchestrator — the built-in create and review types are just the start. Any workflow matching "watch for trigger → run Claude with a prompt → produce an outcome" can be a critter type:

  • Issue triage bot — automatically labels, prioritizes, and summarizes new issues
  • Documentation writer — reads code and generates or updates markdown docs, opens a PR
  • Security auditor — scans for OWASP top 10 vulnerabilities, dependency issues, and hardcoded secrets
  • Test generator — reads implementation code and writes missing test cases, opens a PR
  • Multi-step workflows — chain critter types using Linear/Jira blocking relationships (e.g., a "plan" critter spawns sub-tickets, then "implement" critters pick up each one)

See CLAUDE.md for complete config examples.

Multi-provider (Linear + Jira)

A single daemon can poll both Linear and Jira. Set the default provider at the top level, then use provider on each critter type to override:

provider: linear  # default

jiraStatusMap:
  "Todo": "To Do"
  "In Progress": "In Progress"
  "In Review": "In Review"
  "Done": "Done"
  "Critter Failed": "Failed"

critterTypes:
  create:
    provider: [linear, jira]   # polls both trackers with the same config
    trigger: { label: "Critter", status: "Todo", statusType: "unstarted" }
    repo: { clone: true, branch: true, commitPlans: false }
    phases:
      - name: planning
        prompt: builtin:planning
        model: opus
        maxTurns: 50
        tools: readonly
      - name: execution
        prompt: builtin:execution
        model: opus
        maxTurns: 75
        tools: default
    outcomes:
      success: { status: "In Review" }
      failure: { status: "Critter Failed" }
    concurrency: 2
    timeoutMinutes: 30

provider accepts a single value ("linear", "jira") or an array ([linear, jira]). When an array is used, the type is expanded internally so each provider is polled independently — no need to duplicate config.

Only the env vars for providers you actually use are required. A Linear-only config doesn't need JIRA_* vars.

See CLAUDE.md for full multi-provider docs, Jira differences, and more config examples.

GitHub Issues as a ticket source

The github provider polls GitHub issues in a configured list of repos. Issues are identified as owner/repo#42 everywhere (logs, branches, CLI).

provider: github
github:
  repos: ["myorg/api", "andrew/critters"]   # ticket-source repos (required)
  statusField: "Status"                      # org issue field for statuses (default)
  statusMap:                                 # optional: critter status → GitHub option/label name
    "In Progress": "In progress"
  statusTypes:                               # optional: statusType buckets for triggers
    unstarted: ["Todo", "Backlog"]
    started: ["In progress"]
    completed: ["Done"]

Auth: GITHUB_TOKEN (repo read/write + issues read/write; org admin only needed for auto-creating status field options). Webhooks: set GITHUB_WEBHOOK_SECRET and point a repo/org webhook (events: issues) at /webhook/github.

Statuses are dual-mode, detected per repo at startup:

  • Field mode — for repos in an organization, Critters uses the org's single-select issue field named by statusField (default Status). Create the field once as an org admin (Settings → Planning → Issue fields) with options like Todo / In Progress / In Review / Done; missing outcome options are auto-created when the token has org admin rights.
  • Label mode — personal repos (or orgs whose fields the token can't read) fall back to status:<Name> labels (status:Todo, status:In Progress, …), created and swapped automatically.

Limitations: no attachment uploads (logs are posted as inline comment excerpts instead); status changes made by humans in field mode are discovered by polling, not webhooks (GitHub emits no event for field-value changes); api.github.com only (no Enterprise Server); trigger.statusType only works when statusTypes buckets are configured — otherwise triggers match the exact status name. If you use teamRepos to map issues to clone URLs, key it with the same casing as github.repos (lookup is case-sensitive).

gh version: use gh ≥ 2.96 (or at least newer than 2.45). Older releases — including the gh 2.45.0 shipped by Ubuntu 24.04 — fail gh pr edit with GraphQL: Projects (classic) is being deprecated… (repository.pullRequest.projectCards) on some repos, which silently disables Critters' plan/stats injection into PR bodies (logged as Failed to update PR body with plan (non-fatal)). Everything else still works.

Documentation


See CLAUDE.md for detailed developer docs, architecture diagrams, and contributor conventions.

About

TypeScript daemon that watches an issue tracker for labeled issues and runs configurable agentic Claude Code workflows on them — planning, execution, review, or custom phases

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages