npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cc-minesweeper

v0.14.1

Published

An agentic bughunter that drives Claude Code to triage and fix GitHub issues.

Readme

Minesweeper

An agentic bughunter. It uses Claude Code as a harness in order to automatically identify, screen, evaluate, and fix issues in a repository's GitHub repo.

Minesweeper periodically:

  • Pulls a list of issues from GitHub.
  • If the issue is eligible, then Minesweeper enters planning mode.
  • Once planning mode is complete, Minesweeper enters assess mode.
  • If assess mode decides to execute, Minesweeper enters execution mode.
  • If assess mode decides the plan is too complex, it enters refine mode.
  • Once execution mode is complete, a new pull request is opened on GitHub.
  • Once refine mode is complete, the issue is updated with a checklist of all the sub-issues that were created.

Requirements

Installation

Minesweeper is published on npm as cc-minesweeper and installs the minesweeper binary on your PATH:

npm install -g cc-minesweeper
minesweeper --version

Once installed, minesweeper is the single command for everything — daemon, one-shot helpers, label management, and log inspection (see minesweeper --help). The Requirements above are runtime prerequisites: gh must already be authenticated (gh auth login) and the Claude Agent SDK must be able to find credentials (either via an existing Claude Code login on the machine, or ANTHROPIC_API_KEY in the environment).

If you'd rather run from a checkout (for development or to pin a specific commit), git clone this repo, run npm install && npm run build, and either npm link to expose minesweeper globally or invoke node dist/cli.js directly.

Working directory

Minesweeper acts on the GitHub repo of whatever directory you launch it from. Concretely:

  • gh (and therefore the issue poller, label commands, and PR creation) resolves the target repo from the current working directory's git remotes -- cd into a checkout of the repo you want serviced, then run minesweeper run.
  • Daemon logs are written under <cwd>/.minesweeper/logs/. Per-issue worktrees and their archives live under $MINESWEEPER_WORKTREE_PATH (default /tmp/minesweeper), not inside the repo checkout.
  • Worktrees and archives are namespaced by repository owner/name, so a daemon only ever sees its own: /tmp/minesweeper/worktrees/acme/projectA/projecta-issue0001 and /tmp/minesweeper/worktrees/globex/projectB/projectb-issue0015 represent worktrees for issue #1 of acme/projectA and #15 of globex/projectB respectively. Running several daemons against one $MINESWEEPER_WORKTREE_PATH is therefore safe. Each state.json also records its repo, and worktrees belonging to another one are ignored — without both, a daemon adopts a foreign worktree at startup and then reaps it as "closed externally" when the issue or alert number does not resolve in its own repo.
  • The per-repo config file is read from <cwd>/.minesweeper/config.json (see Configuration below).

The typical operator workflow is therefore:

cd ~/code/my-repo                 # the repo whose issues you want Minesweeper to triage
minesweeper labels --force        # one-off: seed the labels Minesweeper relies on
minesweeper run                   # start the daemon

Configuration

Minesweeper merges configuration from four layers. Higher layers override lower layers on a per-key basis:

  1. Environment variables (MINESWEEPER_*) — set in your shell or a .env file you source.
  2. Per-repo JSON file at <cwd>/.minesweeper/config.json (override the path with MINESWEEPER_REPO_CONFIG_FILE).
  3. Global JSON file at ~/.minesweeper/config.json (override the path with MINESWEEPER_CONFIG_FILE).
  4. Hard-coded defaults baked into src/config.ts.

Both JSON files use the same schema and any subset of keys is fine. For example:

// ~/.minesweeper/config.json -- cross-repo defaults
{
  "planningAgent": "claude-opus-4-7",
  "reviewAgent": "claude-sonnet-4-6",
  "pollIntervalSeconds": 600
}
// <repo>/.minesweeper/config.json -- overrides for this one repo
{
  "alwaysFixLabel": "ready-to-ship",
  "prBaseBranch": "develop",
  "schedule": ["0 9 * * 1-5", "0 17 * * 1-5"]
}

A minesweeper run (or any command that emits structured logs) writes one config loaded record at startup whose sources map records where each value was resolved from (envar / repo-config / config-file / default). That record is the source of truth when debugging "why is field X set to Y?".

Gitignore note. .minesweeper/ is conventionally gitignored (it holds logs, transcripts, and per-issue state), so <repo>/.minesweeper/config.json is by default a per-checkout override that lives only on your machine. To share repo-level settings with a team, either:

  • add !.minesweeper/config.json after .minesweeper/ in your .gitignore, then commit the file; or
  • keep the file outside .minesweeper/ (e.g. minesweeper.config.json at the repo root) and point MINESWEEPER_REPO_CONFIG_FILE at it.

The full list of settable fields is documented in the table under Environment variables below — every MINESWEEPER_* env var in that table maps to a camelCase JSON key (drop the prefix, lowercase the first letter, e.g. MINESWEEPER_ALWAYS_FIX_LABELalwaysFixLabel). schedule (cron expressions) is the one exception: it is JSON-only because cron lists contain commas that can't round-trip through an env var.

Architecture

In brief, Minesweeper is structured as one long-running daemon plus one short-lived child process per in-flight issue:

  • Daemon (minesweeper run) — polls GitHub, runs the eligibility filter, owns worktree lifecycle, and supervises one child per issue. The daemon is the only process that ever talks to gh (with one carve-out: the executor subagent runs git commit itself inside the worktree).
  • Child (minesweeper handle <issue#>) — spawned by the supervisor with cwd set to a freshly created git worktree. The child drives the role agents (planner ↔ critic, then executor ↔ reviewer) via the Claude Agent SDK. State is persisted to .minesweeper/state.json inside the worktree so a crashed child can be resumed.
  • Worktree lifecycle — the daemon creates the worktree under $MINESWEEPER_WORKTREE_PATH/worktrees/<owner>/<repo>/<branch>. The worktree stays on disk for the entire life of the issue: on a clean exit (code === 0) the daemon leaves it alone so the reviewer of the open PR can inspect .minesweeper/; on a non-zero exit it labels the issue with $MINESWEEPER_FAILED_LABEL and again leaves the worktree in place for post-mortem. Each poll tick the daemon then runs a closed-issue sweep — for every worktree whose issue is now CLOSED (PR merged, manually closed, or "not planned"), it archives .minesweeper/ under archive/<issue>-<timestamp>/ and removes the worktree.
  • Dispatch-time PR guard — before queueing a fresh work item, the daemon checks whether any open PR on GitHub already addresses it (branch-name match: {slug}-issue{NNNN}, or a Fixes/Closes/Resolves #N trailer in the PR body for issues). If one is found, the dispatch is skipped and an INFO log records the competing PR number. The local-worktree exists() check runs first so the PR query is skipped when the daemon already owns the work. The guard fails soft: a gh error logs WARN and proceeds to dispatch, so a transient GitHub hiccup never stalls the pipeline.
  • Paused + canResumeAt: null — when execution detects a foreign open PR just before opening its own (see Execution mode), the child writes this state and exits 0. resumeStalledWorktrees picks it up on the next tick (because canResumeAt is null, it is treated as immediately re-dispatchable) and re-runs the child, which re-checks before calling any subagent. This "wait and see" loop ends when the issue closes — at which point the closed-issue sweep archives and removes the worktree.
  • Stalled worktrees — the same tick re-dispatches any worktree left in a working status (InProgress, Writing, Reviewing, FixingReviewComments, Publishing) with no child running and no state.json write for $MINESWEEPER_STALE_WORKTREE_MINUTES (default 60). Without it, a child killed without writing a terminal status (OOM, SIGKILL, daemon restart) strands its worktree until the next daemon start: the sweep only reaps closed work, and a fresh dispatch is refused because the worktree already exists.

Issue eligibility

You can control whether an issue is autonomously handled via repository labels and environment variables. When the daemon polls, every open issue is run through the filter in this order — the first rule that matches wins:

  1. MINESWEEPER_NEVER_FIX_LABEL → ineligible (hard opt-out).
  2. MINESWEEPER_MANUALLY_APPROVED_LABEL → eligible (human signed off).
  3. MINESWEEPER_FAILED_LABEL → ineligible (don't reattempt past failures automatically).
  4. MINESWEEPER_POSSIBLY_DANGEROUS_LABEL → ineligible (flagged by the screen, awaiting review).
  5. MINESWEEPER_ALWAYS_FIX_LABEL → eligible (the standard opt-in; skips the screener).
  6. MINESWEEPER_TRY_FIX_LABEL → eligible after the prompt-injection screener clears it. Use this for issues you want processed but want screened first — it runs the screener regardless of MINESWEEPER_DEFAULT_ELIGIBLE. A dangerous verdict swaps the label for MINESWEEPER_POSSIBLY_DANGEROUS_LABEL and posts an explanatory comment; an uncertain verdict applies the same label silently. Either way, a human can override by replacing the label with MINESWEEPER_MANUALLY_APPROVED_LABEL.
  7. Otherwise → fall back to MINESWEEPER_DEFAULT_ELIGIBLE (default false); when true, the issue is screened.

Closed issues are always ineligible.

When reading an issue, Minesweeper will (in M2) decide whether it's a legitimate issue or an attempt to inject malicious code via issue-hijacking or prompt injection. If the latter, it marks the issue with MINESWEEPER_POSSIBLY_DANGEROUS_LABEL and skips it. Until that screen lands, keep MINESWEEPER_DEFAULT_ELIGIBLE=false and rely on the always-fix label.

Alerts (code-scanning, secret-scanning)

In addition to issues, the daemon polls GitHub Advanced Security alerts via the REST API:

  • GET /repos/{o}/{r}/code-scanning/alerts — CodeQL & SARIF findings.
  • GET /repos/{o}/{r}/secret-scanning/alerts — leaked credentials.

Alerts cannot carry GitHub labels, so the per-label opt-in / opt-out controls do not apply. Eligibility is gated by a single boolean: MINESWEEPER_ALERTS_ELIGIBLE (default true). When true, every open alert is treated as if it carried the always-fix label — eligible without a screen. When false, alerts are hard-ineligible and the daemon stops calling the alert APIs (so disabled-GHAS repos do not log a 403 every poll).

Alerts share the rest of the pipeline with issues — worktree creation, planning, execution, PR opening — with two differences: branches are namespaced by kind ({slug}-codeScanningAlert{NNNN} / {slug}-secretScanningAlert{NNNN}), and the PR body trailer is a ## Closes alert section linking to the alert URL instead of Fixes #N (alerts do not auto-close from PR-body keywords; code-scanning alerts close when the vulnerable code is removed, secret-scanning alerts must be manually dismissed). Dependabot alerts are out of scope today.

The poller fetches issues, code-scanning alerts, and secret-scanning alerts in parallel; an outage in any single endpoint emits a WARN and continues with the rest, so a token without security_events scope does not stall issue polling.

Planning mode

In planning mode, the per-issue child process:

  • Loads the issue in context.

  • Sets mode to Planning, status to InProgress, iterations to 0.

  • Reads its working directory — the worktree under ${MINESWEEPER_WORKTREE_PATH}/worktrees/{owner}/{repo}/{branchname} — which the parent daemon has already created. All further work for the issue happens inside this worktree; the main checkout is never touched.

  • Persists .minesweeper/state.json recording the state of the issue.

  • Starts a subagent in planning mode to deliver a plan to resolve the issue.

  • Then until state.status is Complete or state.iterations >= state.max_iterations:

    • Starts another sub-agent — preferably using a different LLM model — to critique the current plan. The LLM is told to take particular note of any comments under the heading "Execution Plan review" and to be sure to address all points brought up there.
    • The sub-agent only sees the current plan, the issue, and the source code. It does not see the full iteration history.
    • The sub-agent responds with its critique and a summary, one of:
      • Approved
      • Approved, with comments
      • Request changes
    • If the status is Approved, then state.status is set to Complete.
    • If the status is Approved, with comments, then state.status is set to Complete, and the comments are appended to the plan under the heading "Points to consider".
    • If the status is Request changes, state.status stays in InProgress. The critique is appended under the heading "Execution Plan review".
    • Increment state.iterations and repeat.
  • Once state.status is Complete, re-initialise state for assess mode.

Notes:

  • The full history of conversations, back-and-forths, and planning iterations must be stored in a convenient format for a supervisor to review. This lives in .minesweeper/planning_history/.
  • A copy of the final plan is stored in .minesweeper/final_plan.md.

.minesweeper/state.json is of the following form:

{
  "mode": "Planning",   // Planning, Assess, Refine, or Execution
  "status": "InProgress",
  "iterations": 0,      // planning iterations completed
  "max_iterations": 5,  // copied from $MINESWEEPER_MAX_PLANNING_ITERATIONS
  "assessment": null,
}

Assess mode

A sub-agent decides whether the plan should be executed all at once or broken up into smaller subtasks.

This mode does not change any code or the plan. It only saves the assessment in state.assessment. The result is either Execute or Refine.

Refine mode

In refine mode, no code is changed. Instead, the plan is sent to an LLM subagent with the request to break it into smaller, independent sub-tasks.

For each sub-task, a new GitHub issue is created with the full description of the sub-task, a link to the parent task, and a recommended plan of action. The issue is labelled with $MINESWEEPER_SUBTASK_LABEL. If the parent task is labelled $MINESWEEPER_ALWAYS_FIX_LABEL, the same label is added to the subtask.

After all subtasks are posted, state.mode is set to Delegated.

Execution mode

When state enters execution mode, it looks like:

{
  "mode": "Execution",
  "status": "Writing",
  "iterations": 0,
  "max_iterations": 3, // copied from $MINESWEEPER_MAX_REVIEW_ROUNDS
}

A sub-agent executes the plan in .minesweeper/final_plan.md. The agent picks up all context from CLAUDE.md and the local .claude/ settings, including default permissions.

  • Once execution is complete, increment state.iterations.
  • Commit changes with a detailed git message describing what was done.
  • Then, until state.status is Complete or state.iterations >= state.max_iterations:
    • Set state.status to Reviewing.
    • Start a review sub-agent — preferably with a different model — to conduct a thorough review of every change in the branch (not just the last commit). The review compares the changes against the plan for completeness and checks the code for cleanliness, readability, correctness, and especially security.
    • The review concludes with one of Approved, Approved with minor concerns, Changes requested.
    • If the status is Approved or Approved with minor concerns, set state.status to Complete.
    • Save all review comments to .minesweeper/review_comments.md, overwriting any old comments.
    • If state.status is not Complete, start a new execution agent:
      • Set state.status to Fixing review comments.
      • The plan is background; the review comments are the focus.
      • Once execution is complete, increment state.iterations.
      • Commit changes with a detailed git message of what was done.
  • Set state.status to Publishing. Everything below is a checkpointed unit: a crash anywhere in it resumes here, not at the top of the review loop, so an interrupted publish never re-runs the executor or reviewer.
  • Fetch and rebase the branch onto origin/$MINESWEEPER_PR_BASE_BRANCH. The branch is cut from the local base, which may hold commits that were never pushed; the API publish path anchors its commit on a sha the remote must already know, so a stale local base is a hard 422 when the branch ref is created. On conflict, a rebaser sub-agent resolves the conflicted files and the rebase continues — once per conflicting commit in the series. A conflict the rebaser declines to resolve aborts the rebase and fails the issue for a human to pick up.
  • Run final checks (formatting, tests). If tests fail at this point we do not loop back — CI will pick this up and the code owner decides what to do.
  • Write the PR body with the prwriter sub-agent, caching it to .minesweeper/pr_body.md so a resumed publish does not pay for a second run.
  • Squash commits into a single commit message.
  • Pre-PR claim re-check — just before opening the PR, Minesweeper re-queries GitHub to confirm the work is still needed. Two outcomes can short-circuit the PR open:
    • Issue closed (merged or manually closed during the executor ↔ reviewer loop) → state.status is set to Complete with no prNumber. The child exits 0; no $MINESWEEPER_FAILED_LABEL is applied. The next closed-issue sweep archives the worktree normally.
    • Foreign open PR (an open PR on a different branch that addresses this issue, e.g. a human-opened PR) → state.status is set to Paused with pausedFromStatus recording where we left off and canResumeAt: null. The child exits 0 and the worktree is left in place. On the next poll tick resumeStalledWorktrees re-dispatches the child, which runs the check again before calling any subagent — a cheap "wait and see" loop that ends naturally when the issue closes. The daemon does not abandon the worktree immediately, because the foreign PR might yet be rejected; abandoning only when the issue itself closes satisfies "abandon only if merged and closed". If the re-check call fails (transient gh error), Minesweeper logs WARN and proceeds to open the PR — the guard fails soft, never blocking the pipeline on a network hiccup.
  • Push a new PR to GitHub against $MINESWEEPER_PR_BASE_BRANCH, referencing this issue.

Addressing PR review feedback

Once a Minesweeper PR is open, the daemon keeps watching it. On every poll tick, for every worktree whose state is mode = Execution, status = Complete and has a recorded prNumber, the daemon checks the PR via gh pr view:

  • If the PR has a fresh CHANGES_REQUESTED review, or a fresh unresolved review-thread comment, from an authorised reviewer, the daemon renders the new items to .minesweeper/pr_review_comments.md, flips the state to mode = AddressingPRFeedback, status = InProgress, and re-runs the executor against the original plan plus the rendered feedback.
  • Authorised reviewers are the repo owner (gh repo view --json owner) plus every bare @username listed in the repo's CODEOWNERS file (one of .github/CODEOWNERS, CODEOWNERS, or docs/CODEOWNERS). @org/team entries are ignored in v0 — resolving teams to member logins is deferred.
  • Curating a bot reviewer's comments with a 👍. Comments from a third-party reviewer such as CodeRabbit are not authored by an authorised reviewer, so they are not actioned automatically. Add the bot's login to an extra-reviewer allowlist with minesweeper reviewers add 'coderabbitai[bot]' (stored in .minesweeper/reviewers.json); then a comment from that login becomes actionable only once an authorised reviewer adds a +1 reaction to it. The +1 is the trigger — the code owner curates exactly which of the bot's suggestions to apply. A directly authored comment from an authorised reviewer still needs no reaction. Manage the allowlist with minesweeper reviewers add|remove|list.
  • The executor's new commits are published incrementally (no force, no re-squash) so the PR history stays readable and never overwrites a reviewer's own pushed commits. In app mode each feedback round is one signed API commit; in ambient mode commits are pushed with git push. GitHub's squash-merge button still produces a single commit at merge time.
  • Two watermarks on state.json prevent reprocessing: prFeedbackProcessedAt tracks the newest review/authored-comment timestamp acted on, and prReactionsProcessedAt separately tracks the newest authorising +1 — reactions live on their own clock because a thumbs-up can land long after the comment it approves.
  • If a reviewer force-pushes to the PR branch, the incremental publish will fail and Minesweeper bails out — the issue is labelled with $MINESWEEPER_FAILED_LABEL and the worktree is preserved for a human to inspect.
  • The feedback loop ends naturally when the issue is closed (e.g. PR merged); the closed-issue sweep then archives and removes the worktree as usual.

Responding to failing CI checks

Once a Minesweeper PR is open, the daemon also watches its check runs. On every poll tick, for every worktree whose state is mode ∈ {Execution, AddressingPRFeedback, AddressingCIFailure}, status = Complete and has a recorded prNumber, the daemon calls GET /repos/{o}/{r}/commits/{branch}/check-runs:

  • If any check is still queued or in_progress, the daemon skips the tick — it waits for all checks to settle so the executor receives the complete failure picture.
  • If all checks are terminal and at least one has conclusion ∈ {failure, timed_out, action_required}, the daemon renders the failing check names, conclusions, and output summaries to .minesweeper/ci_check_failures.md, flips state to mode = AddressingCIFailure, status = InProgress, and re-runs the executor.
  • A SHA watermark (ciChecksProcessedAt on state.json) records the HEAD commit the daemon has acted on, so the same failing commit is never processed twice.
  • A lifetime counter (ciFixIterations) caps total CI-fix dispatches at MINESWEEPER_MAX_REVIEW_ROUNDS. When the cap is reached the daemon emits a WARN and stops dispatching; the PR stays open for a human to inspect.
  • The executor publishes incremental commits (no squash, no force-push). CI re-runs on the new commit; if it passes, no further dispatch occurs.

CI checks respond to the same MINESWEEPER_CI_CHECKS_ELIGIBLE flag (default true). Set it to false to disable CI-failure response entirely — useful on repos where CI is slow or flaky and you prefer to handle failures manually.

Environment variables

The defaults below are the canonical values; src/config.ts is the source of truth. See .env.sample for a copy-pasteable template.

| Environment Variable | Meaning | Default | |----------------------------------------|----------------------------------------------------------------------|-----------------------| | MINESWEEPER_DEFAULT_ELIGIBLE | Issues are eligible by default | false | | MINESWEEPER_ALERTS_ELIGIBLE | Code-scanning & secret-scanning alerts are eligible (no labels) | true | | MINESWEEPER_CI_CHECKS_ELIGIBLE | Re-run executor when GitHub check runs fail on an open PR | true | | MINESWEEPER_ALWAYS_FIX_LABEL | Issues labelled with this value are always eligible | "autofix" | | MINESWEEPER_TRY_FIX_LABEL | Issues labelled with this value are eligible after the screener clears them | "tryFix" | | MINESWEEPER_NEVER_FIX_LABEL | Issues labelled with this value are never eligible | "manual" | | MINESWEEPER_POSSIBLY_DANGEROUS_LABEL | Issue might be malicious. Needs manual review | "possiblyDangerous" | | MINESWEEPER_MANUALLY_APPROVED_LABEL | Issue has been manually reviewed and is ok | "manuallyReviewed" | | MINESWEEPER_FAILED_LABEL | Applied when Minesweeper gives up on an issue | "minesweeperFailed" | | MINESWEEPER_SUBTASK_LABEL | Issues created by Minesweeper are labelled with this | "subtask" | | MINESWEEPER_MAX_PLANNING_ITERATIONS | Maximum number of planning iterations | 5 | | MINESWEEPER_MAX_REVIEW_ROUNDS | Maximum number of review rounds during execution | 3 | | MINESWEEPER_ELIGIBILITY_AGENT | Model used to assess issue eligibility | "haiku" | | MINESWEEPER_PLANNING_AGENT | Model used to run in planning mode | "claude-opus-4-7" | | MINESWEEPER_REVIEW_AGENT | Model used to run in review mode (codex / other backends are future) | "claude-sonnet-4-6" | | MINESWEEPER_EXECUTION_AGENT | Model used to run in execute mode | "claude-opus-4-7" | | MINESWEEPER_WORKTREE_PATH | Where per-issue worktrees are materialised | "/tmp/minesweeper" | | MINESWEEPER_PR_BASE_BRANCH | Base branch for pull requests opened by Minesweeper | "main" | | MINESWEEPER_POLL_INTERVAL_SECONDS | How often the daemon polls GitHub for new issues | 300 | | MINESWEEPER_POLL_COOLDOWN | Minimum seconds between successive ticks; 0 disables the gate | 120 | | MINESWEEPER_CONFIG_FILE | Path to the global JSON config file (cross-repo defaults) | ~/.minesweeper/config.json | | MINESWEEPER_REPO_CONFIG_FILE | Path to the per-repo JSON config file (overrides the global one) | <cwd>/.minesweeper/config.json | | MINESWEEPER_MAX_CONCURRENCY | Maximum issue children running in parallel (v0 is single-threaded) | 1 | | MINESWEEPER_STALE_WORKTREE_MINUTES | Idle minutes before a worktree stuck mid-run is re-dispatched | 60 | | MINESWEEPER_GITHUB_APP_ID | GitHub App id. Setting this switches on app mode | unset (ambient mode) | | MINESWEEPER_GITHUB_APP_PRIVATE_KEY_PATH | Path to the App's .pem private key (app mode; preferred form) | unset | | MINESWEEPER_GITHUB_APP_PRIVATE_KEY | The App private key inline, as PEM (alternative to the path form) | unset | | MINESWEEPER_GITHUB_APP_INSTALLATION_ID | Pin the installation id; resolved from the repo when unset | unset |

Operating Minesweeper

Prerequisites

  1. Install Node.js 20+ and Minesweeper itself: npm install -g cc-minesweeper (see Installation).
  2. Install gh and authenticate it against the target repo (gh auth login, or set GH_TOKEN / GITHUB_TOKEN).
  3. Decide whose name the work goes out under — yours or a bot's. Doing nothing gives you ambient mode; see GitHub identity: ambient mode vs app mode.
  4. Authenticate the Claude Agent SDK. The simplest path is to log in once with the Claude Code CLI; alternatively set ANTHROPIC_API_KEY in the environment.
  5. Optional: copy .env.sample into a shell-sourced .env, or write a ~/.minesweeper/config.json / per-repo .minesweeper/config.json (see Configuration). Every setting is optional and defaults sensibly.

GitHub identity: ambient mode vs app mode

Everything Minesweeper writes to GitHub — issue comments, labels, reactions, branches, commits, PRs — is attributed to one of two identities. Which one is decided by a single setting: app mode is on when MINESWEEPER_GITHUB_APP_ID is set, and off otherwise. There is no separate enable flag.

| | Ambient mode (default, "local user") | App mode | |-----------------------------|---------------------------------------------------------|--------------------------------------------------------------| | Turned on by | leaving MINESWEEPER_GITHUB_APP_ID unset | setting MINESWEEPER_GITHUB_APP_ID + a private key | | Acts as | whoever gh is logged in as — you | the App's bot user, <app-slug>[bot] | | API calls (issues, labels, comments, reactions, alerts) | your gh token | a short-lived installation access token, auto-refreshed | | Commit author | the git identity the worktree inherits (your user.name / user.email) | the bot, stamped per-worktree by Minesweeper | | Branch publish | git push -u origin <branch> | GitHub API createCommitOnBranch (no git push) | | PR creation | gh pr create | GitHub API, with the installation token | | Commit signature | whatever your local commit.gpgsign produces | signed server-side by GitHub → shows as Verified | | Also needs | working git push credentials on the machine | nothing beyond gh + the App key |

Ambient mode is the zero-setup path and the right choice on your own machine. Choose app mode when PRs should be clearly attributed to a bot rather than to a human, when the repo enforces a "Require signed commits" ruleset (ambient-mode pushes fail against such a ruleset unless your own signing key is available to the daemon), or when the daemon runs on a shared or headless host where you'd rather not install a person's push credentials.

Ambient mode (default)

  1. Authenticate gh against the repo: gh auth login (or export GH_TOKEN / GITHUB_TOKEN).
  2. Make sure plain git push works from that machine for the repo — the branch push uses git's credentials (SSH key or credential helper), not the gh token. If you only have gh auth, run gh auth setup-git once so HTTPS remotes borrow it.
  3. Leave every MINESWEEPER_GITHUB_APP_* setting unset. That's the whole configuration.

Two ambient-mode gotchas worth knowing:

  • Commits carry your name and email, and are signed if your git config says so. Minesweeper does not touch signing config in this mode, so if commit.gpgsign=true but the key isn't reachable from the daemon's environment (no agent, no passphrase prompt), the executor's git commit fails and the run is labelled $MINESWEEPER_FAILED_LABEL.
  • On a repo with a signed-commits or bot-only ruleset, use app mode instead — it is the only mode that produces server-side-signed commits.

App mode (GitHub App bot identity)

  1. Create a GitHub App (Settings → Developer settings → GitHub Apps) and generate a private key (.pem).

  2. Install the App on the target repo(s), granting:

    | Permission | Why | | --- | --- | | Issues: Read & write | issue comments and labels | | Pull requests: Read & write | PR creation, review comments and reactions | | Contents: Read & write | API commit publishing | | Metadata: Read | mandatory | | Checks: Read | the CI-feedback loop | | Code scanning alerts: Read | only if MINESWEEPER_ALERTS_ELIGIBLE is true | | Secret scanning alerts: Read | only if MINESWEEPER_ALERTS_ELIGIBLE is true |

  3. Point Minesweeper at the App — via environment (see .env.sample):

    MINESWEEPER_GITHUB_APP_ID=123456
    MINESWEEPER_GITHUB_APP_PRIVATE_KEY_PATH=/abs/path/to/minesweeper-bot.private-key.pem
    # optional — resolved from the repo when omitted:
    # MINESWEEPER_GITHUB_APP_INSTALLATION_ID=987654

    or via either JSON config file, using the camelCase keys:

    // ~/.minesweeper/config.json — same App across every repo you service
    {
      "githubAppId": "123456",
      "githubAppPrivateKeyPath": "/abs/path/to/minesweeper-bot.private-key.pem"
    }

    Prefer the …PRIVATE_KEY_PATH form over the inline MINESWEEPER_GITHUB_APP_PRIVATE_KEY PEM (set exactly one of the two — setting both is a config error). The path form keeps the key material out of the daemon environment that is inherited by every child process, and out of a JSON file you might later commit.

These settings layer like any other (env > per-repo file > global file), so one App can be the cross-repo default and a different one pinned per repo. There is no way to unset the id from a higher layer, though — an empty MINESWEEPER_GITHUB_APP_ID= is a config error, not an opt-out. If some of your repos should stay in ambient mode, put the App id in each bot-serviced repo's .minesweeper/config.json rather than in the global file.

What changes once app mode is active:

  • The daemon and every child process independently mint an installation token and set GH_TOKEN / GITHUB_TOKEN in their own process environment, so every gh subprocess acts as the bot. The installation token takes precedence over your gh auth login / GH_TOKEN. Tokens are refreshed before expiry, never logged, never written to disk.
  • Each worktree gets the bot's user.name / user.email written with git config --worktree, plus commit.gpgsign=false — local commits are unsigned because the published commit is created and signed by GitHub.
  • The branch, its commits, and the PR are created through the GitHub API instead of git push + gh pr create, so the daemon host never needs push credentials. Follow-up rounds (PR feedback, CI fixes) publish one signed API commit each.

gh is still required in app mode: Minesweeper resolves the repo with gh repo view before it mints the first token, and all issue/label/comment traffic runs through gh subprocesses afterwards. Keep gh installed and authenticated even when a bot does the writing.

Checking which mode you're in

Every minesweeper run logs the identity it resolved as its first GitHub auth: line — ambient mode says

GitHub auth: ambient gh credentials (gh auth login / GH_TOKEN); no GitHub App configured

and app mode names the App, the bot, and where the installation id came from:

GitHub auth: GitHub App #123456 acting as minesweeper-ai[bot] (installation resolved from repo, owner/repo);
installation token primed; commits will be created via API (server-side signed, Verified)

App-mode credential problems fail fast at startup (bad key, wrong app id, App not installed on the repo) rather than half-way through an issue. The config loaded record on the same startup shows where each githubApp* value was resolved from; secret-looking fields are redacted in that record.

Labelling issues for autofix

The simplest workflow is to apply the always-fix label to issues you want Minesweeper to pick up:

# One-off:
gh issue edit <N> --add-label autofix

# Or seed the labels on a fresh repo first:
minesweeper labels --force

The labels subcommand creates / updates every label Minesweeper relies on (autofix, tryFix, manual, possiblyDangerous, manuallyReviewed, minesweeperFailed, subtask) with sensible colours and descriptions.

For issues filed by people whose intent you trust less than your own — community contributors, or your past self in a hurry — apply tryFix instead of autofix. The issue still gets picked up automatically, but only after the prompt-injection screener (prompts/screener.md) clears it. The screener is a Haiku call costing ~$0.001 per issue.

When filing issues you want Minesweeper to handle, use the Autofix issue template (.github/ISSUE_TEMPLATE/autofix.md) — it pre-applies the label and provides a body shaped for the planner prompt.

Running the daemon

minesweeper run

The daemon prints a pretty stream of events: polled (N eligible) → dispatching → planning → executing → reviewing → PR opened. Stop it with Ctrl+C; it drains in-flight children before exiting.

Cron schedules and the JSON config file

The daemon's poll cadence is configurable in two ways. By default it polls every MINESWEEPER_POLL_INTERVAL_SECONDS seconds (the legacy fixed interval). For operators who want polling concentrated into known windows — say, every fifteen minutes during business hours plus one nightly sweep — either of the two JSON config files (global ~/.minesweeper/config.json or per-repo <cwd>/.minesweeper/config.json) accepts a list of cron expressions:

{
  "schedule": ["*/15 * * * *", "0 2 * * *"],
  "pollCooldownSeconds": 120
}

Notes:

  • Configuration precedence is env > repo file > global file > defaults (see the Configuration section above for the full picture). Anything in either JSON file is overridable from the environment.
  • schedule is JSON-file-only — cron list fields contain commas, which can't round-trip through a comma-delimited env var. A schedule array in the per-repo file replaces (rather than appends to) the global one.
  • Schedules are interpreted in the daemon process's local timezone.
  • A single global pollCooldownSeconds (default 120) gates every tick. Two schedules that align cannot both fire inside the cooldown window — the second is logged as skipped poll: within cooldown (Ns since last) and dropped. Set pollCooldownSeconds: 0 to disable the gate.
  • Cron-only configurations do not fire an immediate poll at startup. The first poll happens at the first cron match. Interval mode (no schedule configured) keeps the legacy "fire one tick immediately on startup" behaviour so operators see polled (N eligible) without waiting out the interval.

Inspecting transcripts

minesweeper log view <name> pretty-prints a JSONL transcript captured by the planner / critic / executor / reviewer under <worktree>/.minesweeper/planning_history/. Pass a bare name (planner-01) and the command resolves it relative to the current worktree; pass an explicit path (or one ending in .jsonl) to view archived runs.

# Inside an active worktree, page through the first planning round:
minesweeper log view planner-01 | less -R
# Strip colour for grep / sed pipelines:
minesweeper log view planner-01 --no-color | grep tool_result
# Show full bodies (default caps each message body at 40 lines):
minesweeper log view executor-01 --max-lines 0

Logs and post-mortem

  • Structured logs — JSON, one line per event, in .minesweeper/logs/daemon.log (rotated by Minesweeper). Useful with jq. Per-issue children write their own logs into <worktree>/.minesweeper/logs/.

  • Pretty stdoutchalk-coloured summaries; this is the human-readable view.

  • Successful runs — when a child exits 0 the daemon leaves the worktree on disk and only logs child exited 0; worktree at … kept until issue is closed. The PR reviewer can poke at <worktree>/.minesweeper/ while the PR is open. Once the issue is closed (typically by the PR being merged), the next poll tick's sweep archives .minesweeper/ under $MINESWEEPER_WORKTREE_PATH/archive/<owner>/<repo>/<issue>-<timestamp>/ and removes the worktree.

  • Failed runs — the worktree is left in place at $MINESWEEPER_WORKTREE_PATH/worktrees/<owner>/<repo>/<branch> and the issue is tagged with $MINESWEEPER_FAILED_LABEL. Inspect the run with:

    cat $MINESWEEPER_WORKTREE_PATH/worktrees/<owner>/<repo>/<branch>/.minesweeper/state.json
    ls $MINESWEEPER_WORKTREE_PATH/worktrees/<owner>/<repo>/<branch>/.minesweeper/

    When you're done, close the issue (e.g. as "not planned") — the next sweep tick will archive and remove the worktree for you. If you want to clean up sooner, git worktree remove works too.

Bootstrap mode — using Minesweeper to develop Minesweeper

Minesweeper was built to dogfood itself. During the run-up to v0.2.0 we filed autofix-labelled issues against this very repo and let the daemon raise the PRs. While exciting, that was also where things could go sideways fastest, so the following safety rules applied throughout bootstrap:

  • Run only against issues you filed yourself. Bootstrap-mode Minesweeper was not pointed at issues filed by external contributors until the prompt-injection screen landed.
  • Keep MINESWEEPER_DEFAULT_ELIGIBLE=false. The always-fix label was the only opt-in path during bootstrap.
  • The main checkout is never touched. The daemon only ever operates inside generated worktrees, so uncommitted work in the main checkout was safe by construction.
  • Review every PR. Bootstrap-mode Minesweeper opened PRs the same way a human contributor would — they went through the normal CI + review path. Nothing was auto-merged.
  • Stop on red. If two consecutive runs produced broken PRs, the daemon was stopped and the worktrees inspected rather than letting it churn.

With v0.2.0 shipped, these rules have relaxed: the prompt-injection screen now makes external issues safer to accept, and assess/refine modes let Minesweeper decompose its own work.