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

next-issue

v0.14.1

Published

Autonomous issue harness driven by the Claude Agent SDK

Readme

next-issue

A small harness that works through the open issues of a GitHub repository with the Claude Agent SDK. Run it inside a git repository.

npx -y next-issue@latest

Or install it once:

npm install -g next-issue@latest
next-issue

What it does

For each open issue, oldest first:

  1. Claim the issue only when it holds the status:todo label and holds no status:in-progress or status:in-review label. A status:done, status:needs-human or status:blocked label always stops the claim, a resume too. An issue with saved state under .next-issue/ resumes instead, whatever its other labels.
  2. Add status:in-progress to the issue.
  3. Fetch the remote, fast-forward the local copy of the default branch when that is safe, then add a git worktree at ../<repo>-issue-<n> on the issue-<n> branch. A branch that exists on the remote is the start, and a local branch that fell behind the remote is fast-forwarded. A fast-forward of the base that is not safe leaves the local base branch as it is and writes a base.behind event. A new branch then starts from the remote base.
  4. Let the implementer agent do the work, then commit and push.
  5. Open a draft pull request that closes the issue.
  6. Wait for the checks, once the pull request shows the pushed commit. A pending state waits again, up to checkTimeoutMinutes. A read that fails is tried three times before the run stops. A repository that reports no check keeps asking for checkGraceSeconds, because a new pull request often shows no check yet, and only then goes straight to the review. The later gates of the same issue ask once.
  7. On a red build, give the failed job logs to the fixer agent, then go to step 6 again. The budget is maxCiFixes.
  8. Let the reviewer agent judge the diff, at most diffMaxChars of it. The reviewer reads files and runs commands in the worktree, so it can prove a finding with the tests or the type check. The harness then throws away every change that the reviewer left, so no such change can reach a later commit. The reviewer returns a structured verdict: a summary and one entry per finding, each rated blocking or minor. The findings are the only ground truth. The reviewer gives no approve or reject field, because such a field can contradict the findings. The harness puts the result on the pull request.
  9. An open finding is any finding, blocking or minor, while a review round is left. In the last round, only a blocking finding stays open: a minor finding there goes on the pull request but starts no fix, because a fix in the last round gets no review. Thus maxReviewRounds of 1 never fixes a minor finding.
  10. No open finding means approval: mark the pull request ready for review and set status:done. The merge stays with you. The worktree, the local branch and the state file go away. The remote branch stays. A problem that the reviewer leaves out of the findings thus gets an approval: only the findings count.
  11. With an open finding, run the fixer agent and go to step 6 again. The budget is maxReviewRounds. A fixer that holds every finding to be wrong changes nothing and gives its reason instead. The reason goes on the pull request and into the next round, where it answers every finding of its own round, so the reviewer withdraws them all or repeats them all. With only some findings wrong, the fixer fixes the correct ones, and the next round holds the rest.

Stops for a loop

The harness hands the issue to a person, with the status:needs-human label, when:

  • the check budget or the review budget runs out;
  • a fixer round adds no commit and gives no reason, so there is no progress;
  • the fixer reports that the checks fail for a reason the change did not cause, such as a test already broken on the base branch or one that fails only under load. It changes nothing and the reason reaches the pull request, rather than the fixer spending its whole budget on a failure it cannot fix;
  • a fixer disputes the findings and leaves a change behind, either in the worktree or in a commit of its own. A dispute carries a reason, not a diff, so the harness throws none of it away: an unclean worktree stays on the disk, and a commit of the fixer stays on the local branch. Neither is pushed, so the pull request shows no such change. It holds the reason of the dispute, and the change is on the machine that ran the harness;
  • the reviewer repeats a finding set from an earlier round, which shows a ping-pong between the fixer and the reviewer, or a finding that the fixer disputed and the reviewer holds to. The person then decides;
  • the reviewer gives no verdict, or the checks do not finish in time;
  • an agent does not finish in agentTimeoutMinutes;
  • the setup command fails, or does not finish in setupTimeoutMinutes;
  • a step of the run fails with an error.

The fixer sees the findings of all earlier rounds, not only the last one, with its own disputes next to them. From round two, the reviewer judges only the earlier findings and any regression.

Each agent that writes to the pull request names itself in the first line of the comment: ### Review: approved, ### Review: changes requested and ### Fixer: no change. The account is the same for all of them, so the heading, not the author, tells them apart. The agents read the comments of the issue, not of the pull request, so a comment of an agent never returns to a prompt: the dispute of the fixer reaches the reviewer through the state file.

Stop after the current issue

Run touch .next-issue/stop in a second terminal. The harness reads the file only between two issues, finishes the issue that it holds, deletes the file and then writes the summary as usual. A file from an earlier run is dropped at the start, so it stops nothing.

Ctrl-C is different: the harness passes the signal on to the command that runs and to every child of it, so the run dies in the middle of a step. The saved state lets the next run continue the issue, but the issue keeps status:in-progress and the run writes no summary.

A status label that the harness puts on the issue goes on the pull request too, but only if the pull request exists at that moment. The status:in-progress label of a fresh issue comes before the pull request, and stays on the issue alone. Thus a draft pull request shows work that the harness has not finished, and a status:needs-human pull request shows work that waits for a person.

The harness runs all git and gh commands itself. The commit holds all the changes in the worktree, so a setup command that writes a file outside .gitignore puts that file in the commit. The reviewer is the one agent whose changes never get that far: the harness discards them before it reads the verdict.

Safety

Every agent can run a shell command. The implementer and the fixer run with the permission checks off; the reviewer runs with a smaller tool set, but its shell has no limit on the commands. They all start in the worktree, but nothing holds them there: they can run any command, with your rights, anywhere on the machine. The prompt holds the title, the body and the comments of the issue, which come from GitHub.

Thus a person who can write an issue or a comment can try to give an instruction to the agent. Use the harness only on a repository where you trust the issue authors, or run it in a container.

Observability

Every run has an id, <timestamp>, and writes two files under .next-issue/runs/:

  • <id>.jsonl — one JSON object per event, with ts, runId, kind, and the issue, role or round in scope;
  • <id>.summary.json — token totals per agent role, event counts, and one report per issue with the outcome, the reason, the pull request number and the budgets used.

At the start of a run, the harness deletes the files under .next-issue/runs/ that are older than keepRunDays. Set the field to 0 to keep them all.

A short text summary of the same data goes to standard output. Use --json to get the full summary object there instead, so the harness fits in a pipe. All progress goes to standard error.

Recorded for each step: claim, comments, worktree, setup, implement, push, pull-request, checks, review and fix, each with a .start, .end or .error event and a duration in ms. Every git and gh command is recorded with its exit code and duration. A tool event holds the tool name, and for a Bash tool the command too. Each agent call adds a usage event with the tokens, the cost estimate in dollars, the turn count, the tool-call count, the model and the session id, so you can open the session again and read the full transcript.

jq -r 'select(.kind=="usage") | [.issue,.role,.turns,.total] | @tsv' \
  .next-issue/runs/*.jsonl

Three levels of console output:

| Level | Shows | | --- | --- | | --quiet | Run and issue milestones, verdicts, hand-overs, errors | | default | The above plus each step end, each tool call and failed commands | | --verbose | The above plus every command and all agent text |

Requirements

  • Node 24 or later
  • gh, authenticated with write access to the repository
  • Anthropic credentials for the Claude Agent SDK: ANTHROPIC_API_KEY, or a Claude subscription login

Configuration

To write the file with the defaults, run the init command in the repository:

npx -y next-issue@latest init

The command writes next-issue.config.json in the root of the repository and adds .next-issue/ to .gitignore. It stops when the config file is there already; --force replaces it. The file holds no setupCommand, because the harness deduces one from the lock file of the project.

A field that is not in the file keeps its default. A file that is not valid JSON, a field with a value of the wrong type and a field name that the harness does not know all stop the run, so a typo cannot pass without a word.

{
  "remote": "origin",
  "issueLimit": 100,
  "maxCiFixes": 5,
  "maxReviewRounds": 5,
  "checkIntervalSeconds": 15,
  "checkGraceSeconds": 60,
  "checkTimeoutMinutes": 60,
  "agentTimeoutMinutes": 30,
  "setupTimeoutMinutes": 15,
  "commandTimeoutMinutes": 10,
  "logMaxChars": 20000,
  "diffMaxChars": 60000,
  "keepRunDays": 7,
  "draftPullRequest": true,
  "models": {
    "implementer": null,
    "reviewer": null,
    "fixer": null
  },
  "labels": {
    "ready": "status:todo",
    "inProgress": "status:in-progress",
    "inReview": "status:in-review",
    "done": "status:done",
    "needsHuman": "status:needs-human",
    "skip": "status:blocked"
  }
}

| Field | Default | Effect | | --- | --- | --- | | remote | origin | The git remote for the repository and the branches | | issueLimit | 100 | The maximum number of open issues to read | | maxCiFixes | 5 | The budget for check fixes per issue | | maxReviewRounds | 5 | The budget for review rounds per issue | | checkIntervalSeconds | 15 | The time between two check states | | checkGraceSeconds | 60 | The time to wait for the first check to show | | checkTimeoutMinutes | 60 | The limit for one check wait | | agentTimeoutMinutes | 30 | The limit for one agent call | | setupTimeoutMinutes | 15 | The limit for the setup command | | commandTimeoutMinutes | 10 | The limit for one git or gh command | | logMaxChars | 20000 | The maximum length of the failed job logs, shared between the failed jobs | | diffMaxChars | 60000 | The maximum length of the diff that the reviewer reads | | keepRunDays | 7 | The number of days to keep the run files; 0 keeps them all | | draftPullRequest | true | Open the pull request as a draft, until the review approves | | setupCommand | from the lock file | A shell command to run in a new worktree, before the implementer | | models | null per role | The model per agent role: implementer, reviewer, fixer | | labels | see above | The names of the labels that the harness reads and sets |

An empty labels.ready turns the ready requirement off. The harness then claims every open issue that no other label holds back.

The log gives the reason for a skipped issue as stop-label, not-ready or in-flight.

A role set to null, or with no entry in models, uses the default model of the SDK.

Without a setupCommand, the harness looks in the new worktree for a lock file and runs the install command that goes with it:

| Lock file | Command | | --- | --- | | pnpm-lock.yaml | pnpm install --frozen-lockfile | | package-lock.json | npm ci | | yarn.lock | yarn install --frozen-lockfile, or yarn install --immutable with a .yarnrc.yml | | bun.lock, bun.lockb | bun install --frozen-lockfile | | Cargo.lock | cargo fetch | | go.sum | go mod download | | uv.lock | uv sync | | poetry.lock | poetry install | | Gemfile.lock | bundle install |

The first match in that order wins. A project with no lock file in the list runs no setup, and a setupCommand in the config file always wins. Thus the agents find a worktree with its dependencies in place, and the reviewer can run the tests. Each command holds to the lock file, so a lock file that does not match the manifest stops the issue rather than changing the lock file.

A setupCommand that fails, or that does not finish in setupTimeoutMinutes, hands the issue to a person. The wait for the checks has its own limit, so commandTimeoutMinutes holds for every other git and gh command. State per issue goes to .next-issue/<issue>.json, so a new run continues where the last run stopped.

Retry an issue that waits for a person

A hand-over leaves the state file with the used budgets in it, so the next run would hand the issue over again at once. To give the issue another try, take the status:needs-human label off and run with --reset. The flag puts the budgets and the findings back to zero and keeps the branch, the pull request and the step that the last run reached, so the work carries on where it stopped. A commit that you pushed to the pull request in the meantime is fetched first. The flag touches only an issue that the run handles, so the state of an issue that a label still holds back stays as it was.

Commands and options

| Argument | Effect | | --- | --- | | init | Write the config file with the defaults | | --force | Replace an existing config file, with init | | --once | Stop after the first handled issue | | --max <n> | Handle at most n issues | | --reset | Drop the saved budgets and findings, and keep the rest of the state | | --json | Print the machine summary instead of the text one | | --verbose | Show every command and all agent output | | --quiet | Show only the milestones and the summary | | --help | Show the option list |

The exit code is 1 when an issue needs a person or failed, and 0 when it does not, so a cron job or a CI step can read the result of the run.

Development

npm install
npm run typecheck
npm test
npm run build

npm run build compiles src/*.mts to dist/*.mjs. The package holds the compiled files, because Node does not strip types under node_modules.

Releases

Releases are automatic and need no pull request. Push a conventional commit to main. The workflow reads the commits since the last tag and picks the bump: feat gives a minor, fix gives a patch, and a ! mark or a BREAKING CHANGE body gives a major. Any other type releases nothing.

The workflow then bumps the version and publishes the package to npm with a trusted publish, so no token is stored. Only after the publish does it push the tag and make the GitHub release, so a failed publish leaves no half-release behind. A manual run of the workflow publishes the version in package.json and changes nothing else, which repairs a release that reached GitHub but not npm. The release job stays on a GitHub runner, because npm takes provenance only from a GitHub runner.