Skip to main content

MissCat

PyPI PyPI Downloads

Never miss a single commit.

I’ll catch ’em all, meow.

MissCat is a small CLI that watches a GitHub repository and automatically runs an AI code review whenever a new pull request or commit appears.

It watches the repository, not a single PR.

Why MissCat?

AI reviewers are useful, but somebody still has to notice that a PR changed and ask them to review it again.

MissCat does that part.

new PR
  → review

new commit
  → review again

nothing changed
  → do nothing

No webhook server.
No API keys stored by MissCat.
No repeated review of the same commit with the same profile.

Prerequisites

Before running MissCat, ensure you have:

  • Python: Python 3.9 or newer
  • Git: Configured with credentials to clone and fetch the target repository (HTTPS users using GitHub CLI can run gh auth setup-git)
  • GitHub CLI (gh): Installed and authenticated (gh auth login)
  • At least one AI reviewer CLI: Installed and authenticated:

Installation

Install MissCat with pipx:

pipx install misscat

To upgrade MissCat to the latest version:

pipx upgrade misscat --pip-args="--no-cache-dir"

Releasing (maintainers)

Releases are published to PyPI by .github/workflows/publish.yml when a vMAJOR.MINOR.PATCH tag is pushed. It uses PyPI Trusted Publishing (OIDC), so no PyPI token is stored in GitHub secrets.

One-time setup

  1. On PyPI, add a trusted publisher for the misscat project (Project → Publishing; for a first release use "Add a pending publisher"):
    • Owner: genonfire
    • Repository: misscat
    • Workflow name: publish.yml
    • Environment name: pypi
  2. In the GitHub repository, create an environment named pypi (Settings → Environments). Recommended: add required reviewers so every release needs a manual approval, and add a tag ruleset (Settings → Rules) so only maintainers can create v* tags. A deployment tag rule alone is not enough, since it matches the tag name, not the commit.

Release procedure

  1. Check that the version is not already on PyPI (versions are immutable and cannot be re-uploaded): https://pypi.org/project/misscat/#history

  2. Bump version in pyproject.toml and __version__ in src/misscat/__init__.py (the workflow fails if they differ), then merge to master.

  3. Tag the merge commit and push the tag (the tag must equal v + the pyproject.toml version):

    git tag v1.0.1
    git push origin v1.0.1
    
  4. The workflow fails before publishing if the tagged commit is not on master, the tag is malformed, the tag does not match pyproject.toml or __version__, or python -m build / twine check fails.

  5. Confirm the release:

    pipx upgrade misscat
    misscat --version
    pip index versions misscat
    

Quick Start / First Run

On the first normal run, if ~/.config/misscat/ does not exist, MissCat creates it and copies the bundled reviewer profiles (luna.yml, sol.yml, sonnet.yml, opus.yml, gemini.yml, opencode.yml) into it before loading your profile. Edit them freely. If the directory already exists, nothing is copied automatically, so existing configs are preserved across upgrades. --help and --version never touch the filesystem.

Watch a repository and review new PRs using the default profile:

misscat <owner/repo>

Example:

misscat genonfire/misscat

Use a specific reviewer profile:

misscat genonfire/misscat sol

MissCat resolves sol as:

~/.config/misscat/sol.yml

Running misscat with no arguments (or misscat --help) shows usage help:

misscat --help

Repository names are canonicalized to lowercase, so Genonfire/MissCat and genonfire/misscat refer to the same local workspace and state.

Log output and --loud

By default MissCat logs review status only: review start/done/failed with elapsed time and [cat-N], reviewer progress and usage (reviewer: working, reviewer: turn done (input=…, output=…), reviewer: claude done (turns=…, cost=…)), the nothing to review … polling lines, and every warning and error. Failed reviewer tool activity (exec: failed, a non-zero exit code, a failed MCP or Claude tool call) is a warning and is always shown.

misscat . sol --cat 2 --loud

--loud additionally shows successful reviewer tool activity (exec: …, exec: completed (0), MCP calls, web searches, Claude tool: … lines, Gemini tool steps) and raw, non-JSON reviewer CLI output at DEBUG.

Concurrent reviews: --cat N

misscat . sol --cat 3

--cat N (default 1) runs up to N reviews at the same time, each in its own persistent cat-N worktree. The cats are independent:

  • A finished review is verified against GitHub and recorded immediately, without waiting for the other cats (review done in … [cat-K]).
  • New work is assigned only at the regular poll. A cat that just finished stays idle until the next poll; a completion neither lists PRs nor moves the next poll time.
  • The poll uses the active schedule while at least one cat is reviewing and the idle schedule when none is; changing between the two restarts the schedule.
  • A PR that is being reviewed is not assigned again, even if its HEAD changes meanwhile; the new HEAD is picked up by the first poll after the run was recorded.
  • A HEAD whose review failed is not retried for 5 minutes, and then goes after the other waiting PRs of its priority. A failed workspace sync delays the next poll by at least 5 minutes without penalizing any HEAD; running cats are unaffected.

Using . for the current repository

. always means the GitHub repository of the current Git checkout's origin remote; upstream and other remotes are never used.

misscat .           # watch origin's repository with the default profile
misscat . luna      # same repository, luna profile
misscat state .     # manage that repository's review state

MissCat prints resolved . -> owner/repo and then behaves exactly as if you had typed that name (same state, lock and workspace). It works from subdirectories and worktrees, and understands git@github.com:owner/repo.git, https://github.com/owner/repo(.git) and ssh://git@github.com/owner/repo(.git). It fails, before touching any state, when outside a Git repository, when origin is missing, or when origin is not on github.com (SSH host aliases are not interpreted).

Bundled profiles: misscat init

misscat init          # create the config dir if needed; copy only missing bundled profiles
misscat init --force  # overwrite files named like bundled profiles with fresh copies
  • init never modifies existing files. init --force replaces only luna.yml, sol.yml, sonnet.yml, opus.yml, gemini.yml and opencode.yml; custom profiles, per-repository state JSONs and the review cache are never touched.
  • After upgrading MissCat, new or updated bundled profiles are not applied automatically: run misscat init for new ones, or misscat init --force to reset the bundled ones (this discards your edits to them).
  • If the automatic first-run copy is interrupted, misscat init restores the missing profiles.
  • default.yml is the internal base configuration and is not copied. Each bundled profile carries its own editable copy of the default prompt, so you can customize a reviewer's prompt in place; default.yml remains the fallback.

Review state manager

Stop the watcher before opening the terminal state manager:

misscat state genonfire/typewriter

The list shows the most recently completed reviews first, across all PRs and profiles (sorted by reviewed_at; the stored State order is unchanged). Use ↑/↓ to select any completed review, Enter for its full HEAD SHA, profile, and local review completion time, and Esc/Q to exit. Delete or Backspace only operates on the most recently reviewed HEAD of that PR (by reviewed_at), with a [y/N] prompt that names how many records (one per profile that recorded the HEAD) are removed; reply history is kept. A HEAD with an unsettled review attempt cannot be removed until the watcher has settled it. Older HEADs are read-only until newer ones in that PR have been removed. Nothing is deleted on Enter or when you decline confirmation.

The manager works from local State JSON; it does not call GitHub. Removing a HEAD's records (all profiles) makes that PR/HEAD eligible for review again, but only when the HEAD is the PR's current open HEAD at the next watcher run. Removing historical HEADs does not trigger review of historical commits.

State v2 and process safety

  • Every successful review now stores reviewed_at as an ISO 8601 UTC timestamp. Display uses local time. The review identity is PR + HEAD (the row profile is metadata only); the JSON sorting convention is unchanged.
  • Breaking state reset: stop all previously running MissCat watchers before upgrading to this version; older watchers do not participate in the new OS locking scheme and may restore v1 records from memory. On first State access after upgrade, any v1 reviewed history is intentionally discarded and the file is rewritten as an empty v2 State. Consequently, open PR HEADs previously marked complete become eligible for a new AI review. This may consume additional reviewer tokens. Unknown versions and corrupt State files cause errors instead of being discarded.
  • A per-repository OS file lock prevents a watcher and a state manager—or two watchers—from running on the same repository simultaneously, even with different profiles. Stop the watcher before editing state. The lock file lives under ~/.cache/misscat/locks/ (never in the config directory) and may remain on disk after exit; the OS releases its lock when the process exits. Do not use the file's existence as an indication of an active watcher.
  • State manager needs an interactive TTY. It does not touch profile YAML files or cache workspaces.

Configuration

MissCat ships with a bundled default.yml inside the package, so installed copies do not depend on the source checkout or current working directory.

User configuration and persistent data are kept entirely outside the package:

  • Profiles: ~/.config/misscat/<profile>.yml
  • Per-repository state: ~/.config/misscat/<owner>__<repo>.json
  • Persistent workspace: ~/.cache/misscat/repos/<owner>/<repo>/

Directory layout:

~/.config/misscat/
├── sol.yml
├── sonnet.yml
├── opus.yml
├── genonfire__typewriter.json
└── genonfire__misscat.json

Example default.yml:

reviewer:
  # provider: claude
  # model: claude-sonnet-5-5
  # args:
  #   - --allowedTools
  #   - "Bash(gh *)"

  provider: codex
  model: gpt-6-luna
  args:
    - --sandbox
    - workspace-write
    - -c
    - sandbox_workspace_write.network_access=true
    - -c
    - apps._default.enabled=false
    - -c
    - model_reasoning_effort=max

prompt: |
  Read REVIEW.md if exist and act as the 1st reviewer.
  Use the gh CLI for GitHub operations, including posting the review.

watch:
  idle: [60, 120, 180, 240, 300]
  active: [300, 240, 180, 120, 60]

review:
  include_drafts: true

Example ~/.config/misscat/sol.yml:

reviewer:
  provider: codex
  model: gpt-6.1-sol
  args:
    - --sandbox
    - workspace-write
    - -c
    - sandbox_workspace_write.network_access=true
    - -c
    - apps._default.enabled=false
    - -c
    - model_reasoning_effort=medium

The selected local profile overrides the bundled defaults.

When creating a local profile, use default.yml as the reference and specify provider, model, and args explicitly for that reviewer CLI. Do not rely on args inherited from a different provider.

The args field under reviewer is passed to the selected reviewer CLI unchanged.

Reviewed state is stored per repository and keyed by PR and HEAD SHA, whichever profile ran the review (the profile is kept in each record as metadata only). A HEAD reviewed with luna is therefore not reviewed again by sol or opencode; a new HEAD is.

Gemini (Antigravity CLI)

Allow gh and git in ~/.gemini/antigravity-cli/settings.json (keep your other settings):

{
  "toolPermission": "proceed-in-sandbox",
  "enableTerminalSandbox": true,
  "permissions": { "allow": ["command(gh)", "command(git)"] }
}
reviewer:
  provider: gemini
  model: gemini-3.8-flash-low
  args: [--print-timeout, 30m]

In headless mode a denied permission can end the run without posting a review; use --loud to inspect.

OpenCode

opencode is a regular provider. Run it with your already configured OpenCode CLI like any other profile (opencode.yml is bundled and installed by misscat init). It targets the OpenCode native V2 v2.0.32 interface (V1 flags and events are not supported).

misscat . opencode
misscat owner/repo opencode --cat 2 --loud

~/.config/misscat/opencode.yml is read by MissCat itself (not an OpenCode-native config); it has the usual reviewer (provider: opencode, model, args) and prompt fields, and MissCat runs, inside the exact-HEAD cat-N worktree:

opencode run --standalone --format json <reviewer.args> --model <reviewer.model> <prompt>

Every review starts a fresh OpenCode session, --standalone gives each run a private OpenCode server (never your managed background one), and the child gets PWD set to the worktree. reviewer.model is passed unchanged as provider/model#variant (default openai/gpt-6-luna#xhigh; the variant must exist in your OpenCode).

  • Completion: same as every provider: a submitted COMMENT review with the verdict, the exact HEAD and the run's MissCat-Run: line, never exit status alone. A review submitted before OpenCode fails is still settled.
  • Output: compact progress, tool failures and, when OpenCode reports them, token usage and a cost estimate (summed per step; missing data is omitted, never shown as zero).
  • Fatal errors: clearly non-retryable errors (provider.auth, provider.no-route) assign nothing more, let the reviews already running finish and be recorded, then stop the watch with exit code 2 and advice (opencode auth login, or check reviewer.model in the profile). Other failures follow the normal retry wait.

Upgrading to 1.7.0

  • --cat N still runs up to N reviews concurrently, but reviews are now independent: each result is recorded as soon as it finishes, and idle cats receive new work at the next regular poll. The batch barrier has been removed (see Concurrent reviews). Profiles, state files, locks and worktree paths are unchanged.

  • The separate mscat command is gone: use misscat . opencode. Run misscat init once to install opencode.yml (an existing file is never overwritten). Your edited ~/.config/misscat/opencode.yml keeps working unchanged.

  • A review is now complete per PR + HEAD, not per profile. Existing State v2 files are read as they are: records written under any profile (including mscat/opencode) count, so switching profiles does not review the same HEAD again. Nothing in the files is rewritten or discarded.

BadCat

pipx install misscat also installs badcat, a deterministic watcher of submitted PR reviews (no AI). By default it only reports; it merges (squash) only with --merge.

badcat owner/repo                          # report only, no GitHub writes
badcat . --merge                           # merge after +1 (same as --merge=1)
badcat . --merge=2                         # merge after +1 then +2 (batch-labeled PRs at +1)
badcat . --merge --trusted-reviewer luna   # trust reviews by this login (default: your gh user)

A PR labeled exactly no-review is never eligible: BadCat reports no "ready to merge" for it and never merges it, whatever its reviews or batch/urgent labels. Labels are re-read from GitHub just before a merge, so adding no-review after the PR was listed still blocks the merge; removing the label restores eligibility. The read-only badcat-host Chrome host likewise emits no +1 event for a no-review PR (like a draft), and emits one again once the label is removed. MissCat skips no-review PRs too.

A review counts only if it is a submitted COMMENTED review by a trusted login whose body starts with:

+1
HEAD: <40-hex sha of the PR's current HEAD>

<review evidence>

What MissCat does

  • Watches all open PRs in a repository
  • Discovers PRs created after MissCat starts
  • Detects changes by PR HEAD SHA
  • Reviews every new PR HEAD once, whichever profile ran it
  • Avoids duplicate reviews
  • Remembers reviewed commits across restarts
  • Uses an isolated persistent review workspace
  • Supports multiple reviewer backends

MissCat does not modify code, push commits, or merge PRs.

It watches. It catches. It reviews.

(BadCat is the separate, opt-in merge operator described above.)

Metadata

Release files for misscat 1.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for misscat 1.7.0
File Size Uploaded Provenance Actions
misscat-1.7.0.tar.gz 104.7 kB With provenance Details for misscat-1.7.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for misscat 1.7.0
File Interpreter ABI Platform Actions
misscat-1.7.0-py3-none-any.whl Python 3 none any Details for misscat-1.7.0-py3-none-any.whl

Total release size: 163.5 kB

Release files / misscat-1.7.0.tar.gz

Download URL misscat-1.7.0.tar.gz
Size 104.7 kB
Tags Source
SHA-256 checksum
How to use checksums
51888fd4c70381493b2df2a4580649b34638a65526b8065af341fc5da211160c
BLAKE2b-256 checksum
How to use checksums
8451c57db408ef054c13d42a6c212cab86e3638319e9d2d3ed950143d23c38c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

Release files / misscat-1.7.0-py3-none-any.whl

Download URL misscat-1.7.0-py3-none-any.whl
Size 58.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0ab6a92ee1a10a05a831ea8dfbc7a30f9c644e3935f45d778dc54ec308dd5941
BLAKE2b-256 checksum
How to use checksums
3f14a78d919271886b1fdfad83c027d7670383f1dcbfc0d979bc40c50b49e482
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

Security for misscat 1.7.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for misscat-1.7.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page