issue-metrics: measuring how long your repository actually responds
Gather metrics on issues/prs/discussions such as time to first response, count of issues opened, closed, etc.
At a glance
- What is it?
- issue-metrics is a GitHub Action that searches a repository's issues, pull requests and discussions, computes response-time metrics such as time to first response and time to close, and posts the result as a GitHub issue. It was built by GitHub's OSPO team for their own repos and open-sourced for everyone else.
- Who is it for?
- Adopt issue-metrics if you want a recurring, visible record of how long your repository takes to respond, since the report lands as a GitHub issue that lands in the same place people already look. Do not adopt it to measure individual contributors, since the metrics exclude author comments and bot comments by design.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the action measures and what it deliberately skips
This is a GitHub Action that searches for issues, pull requests and discussions in a repository, measures several metrics, and generates a report in the form of a GitHub issue. The items to search for can be filtered by using a search query.
The metric list is the core of the tool, and it covers the response-time questions a maintainer actually worries about.
Time to First Response is the duration from creation to the initial comment or review. Time to First Review applies to pull requests only and is the duration from creation to the first submitted review. Time to Close is the period from creation to closure. Time to Answer applies to discussions only and is the time from creation to an answer. Time in Label is the duration from label application to removal, and it requires the LABELS_TO_MEASURE environment variable. Time in Draft, for pull requests only, is the duration from creation to the PR being marked as ready for review.
Two exclusion rules matter more than the metric definitions, and the README footnotes them. For pull requests, these metrics exclude the time the PR was in draft mode. For issues and pull requests, comments by the issue or pull request author and comments by bots are excluded.
The draft exclusion is the sensible one. A PR that sits in draft for a week and then gets reviewed in an hour is not a slow review. The author and bot exclusion is the one that shapes the number. Without it, an issue where the reporter replies to a question would register a first response from the wrong side, and a bot posting a lint summary would register a triage that a human did not perform.
That also makes the metric a measure of maintainer attention rather than of activity in general, which is the right definition for the purpose, and it is why the metric should not be read as a per-contributor productivity score.
The README points at the GitHub documentation on searching issues and pull requests and on searching discussions for the query syntax, so the metric set is paired with a documented filter language.
The workflow file that runs the measurement
The Getting Started section asks you to create a workflow file in your repository with specific contents. This is the whole integration, and it is a standard GitHub Actions job.
The first part of the workflow sets up the schedule and the permissions.
name: Monthly issue metrics
on:
workflow_dispatch:
schedule:
- cron: "3 2 1 * *"
permissions:
contents: read
jobs:
build:
name: issue metrics
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: readThe cron runs at 02:03 on the first of the month, and workflow_dispatch is there so you can trigger it by hand. The permissions are the interesting part. The top-level block grants only contents read, and the job then widens it to issues write and pull-requests read. The write permission on issues is what allows the action to open the report, so a workflow that only wanted to compute metrics and print them would not need it.
The next step computes the date range for the previous month and writes it into the environment for later steps.
- name: Get dates for last month
shell: bash
run: |
# Calculate the first day of the previous month
first_day=$(date -d "last month" +%Y-%m-01)
# Calculate the last day of the previous month
last_day=$(date -d "$first_day +1 month -1 day" +%Y-%m-%d)
#Set an environment variable with the date range
echo "$first_day..$last_day"
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"The range is written as a GitHub date range with two dots, and it lands in GITHUB_ENV so that the next step can interpolate it into the search query.
The action step itself passes the token and the query.
- name: Run issue-metrics tool
uses: github-community-projects/issue-metrics@v4
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SEARCH_QUERY: 'repo:owner/repo is:issue created:${{ env.last_month }} -reason:"not planned"'Note the -reason:"not planned" exclusion in the sample query. It filters out issues the maintainers have already declined, which is the kind of detail that separates a report you can act on from a count inflated by closed-as-not-planned entries.
The final step creates the report issue from the generated markdown file.
- name: Create issue
uses: peter-evans/create-issue-from-file@v5
with:
title: Monthly issue metrics report
token: ${{ secrets.GITHUB_TOKEN }}
content-filepath: ./issue_metrics.mdThe action writes issue_metrics.md and a separate action turns that file into a GitHub issue. Splitting the two is what lets you change the report's title and destination without touching the measurement code.
One inconsistency to note: the sample workflow pins the action at v4, while the recent releases listed for the repository are v5.0.3 from 2026-10-01, v5.0.2 and v5.0.1. If you follow the sample verbatim you are on the previous major line.
What the output looks like and where it goes
The output of this action is a report in the form of a GitHub issue, and the README shows a sample of such an issue as an image.
Generating the report as an issue rather than as a comment or a workflow summary is a deliberate choice with consequences. The report becomes a durable, searchable, linkable object in the repository, so it accumulates over time and the history of monthly reports becomes its own record. It also means the report is subject to the same issue triage as everything else, which for a maintainer who has already asked for less incoming issue noise is a fair question to ask before adopting it.
The repository layout shows how the report is assembled, and the file names describe the pipeline. There are markdown_writer.py and markdown_helpers.py for rendering the report, and json_writer.py for a machine-readable version. The metric computations each have their own module: time_to_first_response.py, time_to_first_review.py, time_to_close.py, time_to_answer.py, time_in_draft.py and time_to_ready_for_review.py.
There are two more metric modules beyond the six in the README table. time_to_merge.py exists in the tree, and there are test files for time to merge and time to ready for review, which suggests the shipped feature set has moved past the documented table.
The supporting modules are equally revealing. search.py handles the search query, auth.py handles the token, config.py reads the environment configuration, classes.py holds the data structures, labels.py handles the label-based metric, discussions.py handles discussions, pr_comments.py handles pull request comments, and most_active_mentors.py computes which mentors were most active.
That last one is the most interesting inclusion. A metrics action that names the people who responded most often is a different kind of tool from one that only reports medians and percentiles, and it is the piece that most affects how a team reads the report.
The top-level entries also include json_writer.py, which suggests the same data can be produced in a machine-readable form rather than only read by a human.
Running it outside the workflow: Docker, Python and the test suite
The action is packaged as a container, and the Dockerfile shows the shape of the runtime.
The base image is a pinned python:3.14.7-slim by digest, and the build copies the uv binary in from a pinned astral-sh/uv image. Dependencies are installed with uv sync --frozen --no-dev --no-editable, and the entrypoint is uv run --no-dev --project /action/workspace with the command set to issue_metrics.py.
Using uv rather than pip is what makes the frozen lockfile meaningful. The uv.lock file in the repository is committed, and --frozen means the build uses it exactly rather than resolving afresh, so the container for a given tag does not change underneath you.
The healthcheck is worth a look because it is unusual.
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD ["python3", "-c", "import os,sys; sys.exit(0 if os.path.exists('/action/workspace/issue_metrics.py') else 1)"]It checks that the entrypoint script exists rather than that the process is serving anything, and the Dockerfile comment says as much: to satisfy container scanners. The script is installed with git at a pinned Debian version, and the image carries no shell tooling beyond git.
The Python package metadata is minimal and pins its runtime dependencies exactly: PyGithub at 2.9.1, numpy at 2.4.6, python-dotenv at 1.2.4 and pytz at 2026.5. The requires-python is >=3.11. The exact pins rather than ranges suggest a project where reproducibility of the published action matters.
The test suite is unusually strict about coverage.
uv run python -m pytest -v --cov=. --cov-config=.coveragerc --cov-fail-under=100 --cov-report term-missingA hundred percent coverage floor, with the missing-lines report shown. The Makefile's test target also runs .github/scripts/env_vars_check.sh afterwards, which is a check that the environment variables the action reads are the ones it expects. The lint target runs flake8, isort, pylint and mypy with configuration files in .github/linters/, and then black.
The repository also has a .env-example file and a .devcontainer/ directory, so running the tool locally against a real repository is a supported path rather than an accident.
Who uses it, and why the numbers need context
The README gives four example use cases, and they are worth quoting because they define the audience.
As a maintainer, I want to see metrics for issues and pull requests on the repository I maintain in order to ensure I am giving them the proper amount of attention. As a first responder on a repository, I want to ensure that users are getting contact from me in a reasonable amount of time. As an OSPO, I want to see how many open source repository requests are open/closed, and metrics for how long it takes to get through the open source process. As a product development team, I want to see metrics around how long pull request reviews are taking, so that we can reflect on that data during retrospectives.
Every one of those is a self-assessment use case. None of them is a comparison against other projects or a public leaderboard, which is the detail that keeps this in the category of self-measurement rather than public reporting.
That has an important implication for how you read the output. Time to first response on a repository with one busy maintainer and a repository with a rota of twenty are not comparable numbers, and the tool does not attempt to adjust for that. The value is in the trend over months within one repository, not in the absolute figure.
The OSPO case is the origin story and it explains the design. The action was developed by GitHub OSPO for internal use and open-sourced for anyone's benefit, and the README invites you to inquire about usage by creating an issue in the repository. The metrics are the ones an open source program office would want: response time for external contributors, and throughput of the intake process.
The support section is direct about where questions go. If you need support using this project or have questions about it, please open up an issue in this repository, and requests made directly to GitHub staff or support team will be redirected there. GitHub SLA's and support/services contracts do not apply to this repository.
There is also a repository transfer to be aware of. The README notes that the repository has been transferred from github/issue-metrics to github-community-projects/issue-metrics, and asks that references to the old location be updated. Any workflow that pins the old path will fail.
Where issue-metrics is the wrong tool
There are three limits, and the first is about what the metric can carry.
The first is that these are medians and distributions of a small population, and the report is a monthly snapshot of it. If a repository receives twenty issues a month, the time to first response statistic is computed over twenty items, and one maintainer having a bad week moves it. The tool does not and cannot make a single month's number meaningful on its own, and the README does not describe any statistical treatment of the distribution beyond what a Markdown table can show.
The second is the report lands as an issue. That is convenient, and it also means the repository accrues one issue per reporting period, forever, on a schedule you chose. If your repository has a strict policy against incoming issue noise, or if the maintainers are the ones being measured, this can land badly. The example use cases show the intent is self-assessment, and that intent depends on everyone involved reading it that way.
The third is scope. The action measures the things listed in the table and nothing else, it works against GitHub's search API rather than against an exported dataset, and the search query is the only real filter. If you want to measure across repositories, or want to combine this with issue content analysis, or want the data in your own warehouse rather than in a Markdown issue, this is the wrong tool. There is a JSON writer in the repository, which suggests the data is available in a machine-readable form, but the reporting target is a GitHub issue.
There is also a small operational gotcha. The action needs the issues: write permission to create the report, and it needs a token, and the sample workflow uses the automatic GITHUB_TOKEN. If your organisation restricts that token, the report step fails, and the failure mode is a workflow that measured everything correctly and then could not write the result.
The honest alternative for the same question is the metrics your own GitHub data already gives you. The REST API exposes created_at and the timeline of comments, and the GraphQL API exposes review times and merge times, so a scheduled job that queries the API directly and writes to whatever dashboard you already use answers most of these questions without adding an action to your repository. The difference in approach is that issue-metrics handles the exclusions, the draft adjustment and the report formatting for you, and it puts the answer where a maintainer will see it without building anything.
What issue-metrics offers over a hand-rolled script is consistency and a shared definition of the metric across repositories, which matters if you want to compare your own projects over time or across teams. The cost is a dependency on the action's release cadence, a report in the issue tracker, and a definition of the metric that you do not control.
Editorial conclusion
Adopt issue-metrics if you want a recurring, visible record of how long your repository takes to respond, since the report lands as a GitHub issue that lands in the same place people already look. Do not adopt it to measure individual contributors, since the metrics exclude author comments and bot comments by design. Before you wire it up, note the transfer from github/issue-metrics to github-community-projects/issue-metrics, and that the sample workflow pins the action at v4 while the newest release is v5.0.3.
Frequently asked questions
What does the issue-metrics GitHub Action measure?
It searches a repository's issues, pull requests and discussions and computes metrics including time to first response, time to first review for pull requests, time to close, time to answer for discussions, time in label, and time in draft for pull requests. The output is a report published as a GitHub issue.
How do I set up the issue-metrics workflow?
Create a workflow file such as .github/workflows/issue-metrics.yml with a scheduled or manually triggered job, then edit the SEARCH_QUERY and assignees values in the sample. The workflow needs issues: write permission to create the report, and the action is invoked with github-community-projects/issue-metrics@v4 in the sample.
Which comments are excluded from the issue-metrics numbers?
For issues and pull requests, comments by the issue or pull request author and comments by bots are excluded. For pull requests, the metrics also exclude the time the PR was in draft mode.
How does issue-metrics measure time in label?
Time in Label is the duration from label application to removal, and it requires the LABELS_TO_MEASURE environment variable to be set. The metric is computed by the labels module in the repository.
Has the issue-metrics repository moved?
Yes. The README states that the repository has been transferred from github/issue-metrics to github-community-projects/issue-metrics and asks that any references to the old location be updated, so a workflow pinning the old path will no longer resolve.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/github-community-projects-issue-metrics)Was this useful?