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

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) 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.

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 and gemini.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 entry for that PR and profile (by reviewed_at), with a [y/N] prompt. Older entries are read-only until newer entries in that PR/profile group 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 record makes that PR/HEAD/profile combination 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. Existing PR/HEAD/profile identity and the JSON sorting convention remain 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, HEAD SHA, and profile. The same HEAD can therefore be reviewed again with a different profile.

Gemini (Antigravity CLI)

Gemini reviews use the Antigravity CLI (agy). Configure authentication and permissions before running MissCat.

For headless reviews, configure the Antigravity CLI settings file:

~/.gemini/antigravity-cli/settings.json

{
  "toolPermission": "proceed-in-sandbox",
  "enableTerminalSandbox": true,
  "permissions": {
    "allow": [
      "command(gh)",
      "command(git)"
    ]
  }
}

Preserve any existing settings when adding these fields. Ensure the repository workspace is trusted as appropriate.

Example reviewer profile (~/.config/misscat/gemini.yml):

reviewer:
  provider: gemini
  model: gemini-3.8-flash-low
  args:
    - --print-timeout
    - 30m

Important: In headless mode, commands requiring interactive permission approval may be automatically denied. Antigravity can still exit successfully without posting a GitHub review.

If a review finishes without appearing on GitHub, run MissCat with --loud to inspect reviewer activity and permission errors.

Avoid --dangerously-skip-permissions for routine unattended operation, as it broadly bypasses tool approval checks.

Review workspace

MissCat reviews each repository in its own persistent workspace:

~/.cache/misscat/repos/<owner>/<repo>/

The repository is cloned on first use and reused for later reviews. Before each review, MissCat prepares a clean checkout of the exact PR HEAD.

Your normal development checkout is never touched.

Requirements and limits

  • Authentication expectations:
    • Git must be able to authenticate to the target repository for clone and fetch. Existing SSH keys or Git credentials work seamlessly. If using HTTPS with GitHub CLI, configure Git helper via gh auth setup-git. MissCat never stores Git credentials.
    • GitHub CLI (gh) must be authenticated (gh auth login) with permissions to query pull requests.
    • Reviewer CLIs (claude, codex, agy) must be logged in and configured with their respective provider accounts or API keys. MissCat uses their existing credentials.
  • Trusted repository and PR warning:
    • Reviewer CLIs execute within a MissCat-managed checkout of PR-controlled files.
    • MissCat should only be used with repositories and pull requests whose code and contributors you trust.
  • One process per repository:
    • Run at most one MissCat process per repository at any given time.
    • Multiple MissCat instances or profiles pointing to the same repository would share and conflict over the same persistent workspace (~/.cache/misscat/repos/<owner>/<repo>/).

Reviewer backends

MissCat is not tied to a specific AI reviewer.

Initial backends:

  • Claude Code CLI
  • Codex CLI
  • Gemini via agy(Google Antigravity CLI)

MissCat runs the selected reviewer CLI from the root of the prepared PR checkout. The CLI can therefore discover and apply its own repository instructions, such as CLAUDE.md or AGENTS.md, while REVIEW.md defines the review behavior requested by MissCat.

MissCat does not parse or translate those instruction files.

MissCat uses the authentication already configured in the reviewer CLI. The args field under reviewer can be used for CLI-specific execution options such as tool permissions, sandbox settings, or network access.

GitHub repository and pull request access is handled through the authenticated GitHub CLI (gh).

MissCat does not manage GitHub tokens, Git credentials, or AI API keys itself.

Adaptive polling

When there are no open PRs, MissCat gradually becomes lazy:

1m → 2m → 3m → 4m → 5m → 5m ...

After review work is complete, MissCat gives the author some time to make changes, then gets increasingly impatient:

5m → 4m → 3m → 2m → 1m → 1m ...

MissCat runs one review at a time.

When a review finishes successfully, it immediately checks the repository again before sleeping. If another PR or new HEAD is waiting, it reviews that next.

If a review or workspace preparation fails, that HEAD is not marked reviewed and MissCat waits 5 minutes before checking again.

BadCat: review-state watcher and opt-in merge

pipx install misscat also installs BadCat, a second CLI from the same package and version. BadCat is not an AI reviewer: it deterministically watches submitted GitHub PR reviews, reports state transitions, and squash-merges only when started with --merge.

misscat owner/repo luna     # AI review (unchanged)
badcat owner/repo           # DEFAULT: monitor and report only; NO GitHub writes
badcat owner/repo --merge   # monitor, report, and squash-merge eligible PRs
badcat . --merge --trusted-reviewer luna --required-check gate
  • . resolves to the current repository's origin exactly as in MissCat.
  • All open PRs are watched, Draft or Ready alike. BadCat never reads, changes or decides from Draft status; if GitHub rejects a merge of a Draft PR, the rejection is reported and BadCat keeps watching.
  • No AI calls, no checkout, no clone. It uses your existing gh login (gh auth login); no tokens are stored. --merge needs a gh account with permission to merge.
  • Output reports transitions only, not repeated snapshots: new PR, new HEAD, each newly submitted review, CI/merge waits and readiness, merged or closed, and API errors. Each new review is announced as one line carrying its literal first line, whatever it says (PR #290: +1, PR #292: ## 1차 리뷰 — 변경 요청); no INVALID label or explanation is added to a nonconforming review. What is printed is separate from the strict validation below. When BadCat sees a PR for the first time it records the existing reviews silently and announces only later ones. Polling is every 60 seconds, backing off to 5 minutes on errors.
10:00:00 INFO Watching owner/repo (notify only; trusted reviewers: luna)
10:01:12 INFO PR #290: +1
10:03:25 INFO PR #290: -1
10:10:42 INFO PR #290: new HEAD (e4f5678)
10:15:08 INFO PR #290: +2
10:15:09 INFO PR #290: ready to merge (notify only)

Review header

Only a submitted review (GitHub state COMMENTED; not issue comments, inline-only comments or APPROVE) counts, and only if its raw body starts with exactly:

+1
HEAD: <40 lowercase hex characters>

<review evidence>

The first line is exactly +1, +2 or -1: no leading blank or whitespace, decoration or explanation (-1 : FAIL (blocker found) is printed as-is but never authorizes). The second line is HEAD: <sha> immediately after. Evidence must follow. The header SHA, the review's commit_id and the PR's current HEAD must all match, so reviews of an old HEAD never carry forward.

For the current HEAD a valid +1 followed later by a valid +2 makes the PR eligible, provided no later trusted review on that HEAD is something else: a CHANGES_REQUESTED or a plain/headerless COMMENTED review after the first +1 holds the PR (its first line is still printed as usual) until a new HEAD is reviewed. APPROVED and DISMISSED reviews neither pass nor hold. A -1 anywhere on the HEAD blocks it, a malformed or contradictory marker review on the HEAD also prevents merging, and +2 alone never authorizes. Reviews that are not COMMENTED (for example CHANGES_REQUESTED or APPROVED) never count. A new commit requires fresh reviews.

Trust

Only reviews by trusted logins count (fail closed). Use --trusted-reviewer LOGIN (repeatable); the default is the authenticated gh user. Stage 1 and Stage 2 may share one account.

Merge gate (--merge only)

Immediately before the only write, BadCat re-reads the PR HEAD and all reviews and re-evaluates everything, then requires:

  • the exact-HEAD check runs and commit status to be complete and passing (pending, failed, stale or missing CI fails closed), and every --required-check NAME to have a success run on that HEAD (a skipped job does not count). --merge refuses to start without at least one --required-check: BadCat cannot reliably discover a repository's required checks, so an unrelated passing check never opens the gate,
  • GitHub to report the PR mergeable with a clean mergeable_state (so branch protection is honored).

It then sends a squash merge with the expected HEAD SHA, so GitHub rejects it if the HEAD moved. There is no fallback to an unguarded merge. A rejection is logged once and not repeated until the PR's state changes; already merged or closed PRs are handled quietly. Disable any other automatic merger for the repository when using --merge.

State and process safety

BadCat keeps its own notification-dedupe state in ~/.config/badcat/ and its own lock in ~/.cache/badcat/locks/, separate from MissCat's. State never authorizes a merge (GitHub is always re-read), and unreadable state is ignored. One BadCat process per repository; MissCat and BadCat can run on the same repository at the same time.

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 per profile
  • 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.2.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.2.0
File Size Uploaded Provenance
misscat-1.2.0.tar.gz 52.2 kB With provenance Details

Built distribution (wheel)

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

Total release size: 88.8 kB

Release files / misscat-1.2.0.tar.gz

Download URL misscat-1.2.0.tar.gz
Size 52.2 kB
Tags Source
SHA-256 checksum
How to use checksums
42ddca90747dd8b3513f1dd5b89263de0677e61d100174e9719fdc59305b3be1
BLAKE2b-256 checksum
How to use checksums
bec1f8fd2e66115e688bc8e31e99c09bf2adcb30a568937f33027cc0c7f9e226
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 5, 2026.

Transparency log

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

Download URL misscat-1.2.0-py3-none-any.whl
Size 36.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
77ead5ddc4620280fe29a9cf72a68aab3012300f896accb975a30d5ebb6c4d13
BLAKE2b-256 checksum
How to use checksums
892a55f4a5ada666ba3070c9bb35516a6ebe3c774c1f79f5466ab9330192a642
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 5, 2026.

Transparency log

Security for misscat 1.2.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.2.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 Oct 5, 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