# GitHub Issue Writer: File Audit Findings, Skip Duplicates (`aiqlabs/dataset-to-github-issues`) Actor

Takes the dataset an audit Actor produced and opens a GitHub issue for each confirmed finding, through an MCP connector. It reads the tracker first, so running it again does not file the same finding twice, and it refuses to file anything the audit could not verify.

- **URL**: https://apify.com/aiqlabs/dataset-to-github-issues.md
- **Developed by:** [Ai-Q Labs](https://apify.com/aiqlabs) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## GitHub Issue Writer: File Audit Findings, Skip Duplicates

An audit run ends with a dataset. A dataset is a place findings go to be correct in private.

This Actor takes that dataset and opens a GitHub issue for each confirmed finding, through an
[Apify MCP connector](https://docs.apify.com/platform/integrations/mcp-connectors). It reads the
tracker before it writes anything, so running it again does not file the same finding twice.

It was built to sit downstream of [`github-repository-audit`](https://apify.com/aiqlabs/github-repository-audit),
but nothing about that Actor is hard-coded here. Any dataset whose rows carry `riskLevel` and
`issueCodes` works.

### What it does with a finding

The audit it reads from answers in three states, not two:

| State | What it means | What happens here |
|---|---|---|
| Found something | A check ran and failed | An issue is opened, if it is at or above your risk threshold |
| Found nothing | A check ran and passed | Skipped |
| Could not check | The check never ran (a rate limit, a network error) | **Never filed on its own**, and never dropped silently |

The third state is the reason this Actor exists in the shape it does. Filing it turns "I don't know"
into an alarm. Dropping it turns an unanswered question into a clean bill of health.

So a row whose only content is an unverified check comes back as `withheld`, and a row that has both
a real finding and an unverified check carries the caveat into the issue body:

```markdown
#### What the audit found
- `deprecated_on_registry` (high) — The registry marks this deprecated: "Package no longer supported..."

#### What the audit could not check
- `not_checked` — aceakash/string-similarity was not checked: GitHub's hourly allowance ran out.

These are open questions, not clean results. Re-run the audit to close them.
```

### Deduplication, without a state file

Every issue is titled `[dep-drift] <key> — <lead finding>`. A later run lists the open issues, parses
the key back out of titles it wrote itself, and skips anything already covered. There is no state
file and nothing to fall out of sync with the tracker.

If the read fails, the run stops before writing. Duplicates in someone's tracker cost more than a
failed run.

### Setting up the connector

1. In Apify Console, open **Integrations → MCP connectors → Add new MCP connector**.
2. GitHub is not one of the presets. Type the server URL directly:
   `https://api.githubcopilot.com/mcp/`
3. Console probes the server and offers **API key**. Paste a GitHub
   [fine-grained personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)
   scoped to the tracker repository with **Issues: read and write**. Nothing else is needed.
4. **Check the server URL again immediately before you press Save.** In my setup it silently reverted
   to the first preset in the list while I was pasting the key, with a green validation check still
   showing.

The Actor never sees that token. It authenticates to the Apify MCP Proxy with its own run token, and
the platform injects the credential server-side.

#### Why it can only touch issues

The connector discovers whatever the server supports — 44 tools, in my case, including
`create_repository`, `delete_file` and `merge_pull_request`. This Actor declares four of them in its
input schema:

```json
"mcpServers": [
    {
        "url": "https://api.githubcopilot.com/mcp/",
        "tools": { "required": ["issue_read", "issue_write", "list_issues", "search_issues"] }
    }
]
```

The proxy enforces that declaration. A run of this Actor sees four tools and cannot call a fifth,
whatever the connector or the token would otherwise allow. The run log states the number it got:

```text
INFO  The proxy exposes 4 tool(s) to this run: issue_read, issue_write, list_issues, search_issues
```

### Input

| Field | Required | What it is |
|---|---|---|
| `datasetId` | ✓ | The audit dataset. Read access only, declared in the schema — this Actor never writes back into your audit results |
| `githubConnector` | ✓ | An MCP connector authorized against GitHub |
| `owner` / `repo` | ✓ | The tracker the issues go into. This is not the audited project |
| `minRiskLevel` | | `critical`, `high` (default) or `medium` and above |
| `maxIssues` | | Ceiling on writes per run, default 10. Findings past it are reported as `deferred`, not dropped |
| `dryRun` | | **On by default.** Reads the tracker, decides, reports, writes nothing |
| `labels` | | Applied only if the label already exists; this Actor does not create labels |

Leave `dryRun` on for the first run against a repository you care about.

### Output

One dataset row per audit row, with the decision (`file` / `filed` / `skipped` / `deferred` /
`withheld` / `unreadable`), the reason in plain English, and the issue number and URL for anything
filed. Plus two key-value records:

- `SUMMARY` — counts, the tools the proxy exposed, and anything that could not be recorded
- `TOOLS_VISIBLE_TO_THIS_ACTOR` — the full schema of every tool the run could actually see

### Honest limits

- **`withheld` has never fired on live data.** It has unit tests and it is correct as written, but in
  178 audited packages every unverified row also carried a real finding. The whole-row case is rarer
  than I assumed when I built it.
- **The issue number comes out of the URL.** `issue_write` replies with `{"id": "...", "url": "..."}`
  and no `number` field, and that `id` is an internal identifier, not the issue number. This Actor
  parses the URL rather than recording a plausible wrong number.
- **A failed dataset write does not fail the run.** By the time the report is written the issues are
  already open, and a FAILED run reads as "nothing happened". Problems are listed in
  `SUMMARY.reportProblems` instead.

### Running it locally

Local runs cannot reach MCP connectors; `APIFY_MCP_PROXY_URL` only exists in a platform run. Unit
tests run anywhere:

```bash
npm install
npm test
```

### License

ISC. See [LICENSE](LICENSE).

# Actor input Schema

## `datasetId` (type: `string`):

The dataset produced by an audit run. Each item is expected to carry riskLevel and issueCodes; anything else is counted as unreadable rather than treated as clean. This Actor asks for read access only — it never writes back into your audit results.

## `githubConnector` (type: `string`):

An MCP connector authorized against GitHub. This Actor can only read issues and open them; the proxy rejects every other tool, whatever else the connector supports.

## `owner` (type: `string`):

Owner of the repository the issues are filed in. This is the tracker, not the audited project.

## `repo` (type: `string`):

Repository the issues are filed in.

## `minRiskLevel` (type: `string`):

Rows below this level are left in the dataset. A row whose only finding is not\_checked is never filed at any setting, because 'could not verify' is not a defect.

## `maxIssues` (type: `integer`):

A ceiling on writes. Findings beyond it are reported as deferred, not dropped silently.

## `dryRun` (type: `boolean`):

Do everything except the write: read the tracker, decide what would be opened, and report it. Leave this on for the first run against a repository you care about.

## `labels` (type: `array`):

Applied only if the label already exists in the tracker; the Actor does not create labels.

## Actor input object example

```json
{
  "datasetId": "abcDEF123456",
  "owner": "ai-q-labs",
  "repo": "github-repository-audit",
  "minRiskLevel": "high",
  "maxIssues": 10,
  "dryRun": true,
  "labels": []
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("aiqlabs/dataset-to-github-issues").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("aiqlabs/dataset-to-github-issues").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call aiqlabs/dataset-to-github-issues --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,aiqlabs/dataset-to-github-issues"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Xcgnee6ko8QczvnQh/builds/wGnqHaeNTVDo0slsW/openapi.json
