Skip to content

Review gate for AI-generated code

Looks fine in review. Breaks in production.

Unslop reviews every pull request for the ways AI-written code fails: swallowed errors, packages that don’t exist, SQL built from strings, tests that assert nothing.

A deterministic pre-scan runs first. Then a model reviews the diff, and a second model re-checks its findings without seeing the first one’s reasoning. Every finding names its rule and quotes the code.

How the verdict is made

Private beta. Sign-up is closed, and the CLI, the editor extension and the MCP server are not published yet.

Show the finding. Explain the verdict. Say what was skipped.

unslopai/test · pull request #20

src/lib/user-lookup.ts

1+import type { Pool } from 'pg';
2+
3+/** Loads a user row by id. */
4+export async function loadUserById(pool: Pool, userId: string) {
5+ return pool.query(`SELECT id, email FROM users WHERE id = ${userId}`);
6+}
unslop-gatekeepersrc/lib/user-lookup.ts:5

SEC-004 (SQL built by string interpolation) (CRITICAL)

The SQL statement is assembled from interpolated/concatenated values — SQL injection. Use parameterized queries exclusively.

Anti-Slop Gatekeeper · 16 sfailure · 1 critical slop finding
Real output, not a mock-up: the check run and the review comment on pull request #20 in our own test repository, 2026-09-30. This finding comes from the deterministic pre-scan.

Show, don’t claim

Two more findings, copied as stored

Both come from scans of our own test repository. Rule, quoted code, explanation and fix are product output, unedited. The product writes its findings in English.

Model finding, confirmed by the blind re-check

Condition 1 (Silent Error Swallowing)

CRITICALconfirmedfix-probe.ts:4

Quoted code

    } catch (e) {
        console.error(e);

Suggested fix

    } catch (e) {
        throw new Error(`Failed to parse configuration: ${e instanceof Error ? e.message : String(e)}`);
    }

The catch block performs passive logging and returns null instead of propagating the error or triggering a compensating mechanism, silently swallowing parsing failures.

CLI scan of 2026-09-17, job 0cf82b14. The reviewing model drafted the finding. The second model confirmed it from the rule and the quoted code alone.

Model finding, downgraded because a stage was skipped

MAINT-007

WARNINGunverifiedsrc/lib/prescan/companion-loader.ts:138

Quoted code

                if (listError instanceof GitHubApiError && listError.status === 404) return null;

Suggested fix

                if (readError instanceof GitHubApiError && readError.status === 404) return null;

The catch block catches 'readError', but the condition references 'listError', which is out of scope. This causes a TypeScript compile error (TS2304: Cannot find name 'listError') and a runtime ReferenceError whenever fetchFileContent throws an error.

⚠️ Downgraded to WARNING — this finding was flagged for Pro escalation, but the escalation was skipped (budget, API or time-limit degradation), so it could not be conclusively verified.

Pull request #17, 2026-09-26, job f2c676ba. For this run we cut the time budget to 150 seconds on purpose, to exercise this path. The third pass did not run, so the finding was downgraded to a warning, and the comment says why.

01

Deterministic first

65 rules are checked by code, not by a model.

The pre-scan runs before any model call. It uses pattern matchers, syntax-tree queries, config checks, lint rules and package-registry lookups, and it has a detector for 65 rule IDs.

A model never re-judges a deterministic finding. If the model reports the same rule at the same place, the deterministic finding wins. In the result it is marked deterministic.

02

Model review, re-checked blind

A second model re-checks each finding without the first one’s reasoning.

The rule catalogue has 127 entries. Rules without a detector need judgement, so a model reviews the diff against the rules that apply to your stack. Some rules describe process and cannot be checked on a diff at all. What the model reports is a claim, not yet a finding.

A second model gets only the rule, the location, the quoted code and the diff. It does not see the first model’s explanation, severity or fix. It answers confirmed, refuted or uncertain. Refuted claims are removed. Uncertain ones, and critical ones that are refuted or weakly confirmed, get a third pass. What stays uncertain becomes a warning and says so.

Two things to know. All models are Gemini models from one vendor: the independence comes from what the second model is not shown, not from a different model family. And not every finding gets the blind re-check: large diffs can be reviewed in a single pass without it, and the review then says so.

03

When a stage is skipped

A skipped stage is reported, not hidden.

Every review has a time budget of 300 seconds. When the re-check or the third pass cannot run because of time, a model error or an exhausted budget, the findings are not passed off as verified. A finding that was waiting for the third pass is downgraded to a warning with a note. If the re-check did not run, the confidence score reads n/a instead of a number.

The pull request, the check run and the CLI then carry the same line, for example: “Reduced confidence: time budget exhausted — skipped: blind verification (some or all findings).” If the model review fails on some files, those files are listed by name.

Who decides

Which part of the verdict comes from where

Which part of the verdict comes from where
QuestionDeterministic pre-scanModel review
Rules65 rule IDs with a detectorRules that need judgement, from a catalogue of 127
How a finding is producedCode: patterns, syntax tree, config checks, lint rules, registry lookupsOne model drafts it, a second model re-checks it blind
FilesTypeScript, JavaScript, Python, Java, Go, Rust, C and C++, plus config files such as YAML, JSON, Terraform and TOMLTypeScript, JavaScript and Python only
Can it fail the check?Yes. A CRITICAL finding fails the check.Yes. A CRITICAL finding fails the check, so a verdict can rest on a model finding.
Marked in the result asdeterministicconfirmed, uncertain or unverified

The problem

What gets through review

“It ran without errors.”

Because the error was caught and dropped. A catch block that logs and returns null looks tidy in a diff and hides the failure in production. The confirmed finding above is exactly this.

“The import looked plausible.”

The package name was made up. Unslop looks up the dependencies declared in package.json, requirements.txt, pyproject.toml and Cargo.toml on their registry (rule SEC-035).

“The tests are green.”

A test without an assertion is always green. Assertion-free tests are one of the deterministic rules (TEST-001).

“I didn’t read all forty files.”

The agent changed more than anyone read. Unslop reviews every push to the pull request again and comments on the line in question.

Platform

One review, four surfaces

Pull request, terminal, editor, coding agent.

Rule catalogue
127 rules, each with an ID. 65 have a deterministic detector. The model review is given the rules that apply to your stack; some rules describe process and cannot be checked on a diff.
PR gate
GitHub App. Every review is a check run named “Anti-Slop Gatekeeper” plus comments on the lines. A CRITICAL finding fails the check; warnings conclude as neutral.
Repository context
For connected repositories, signatures and type definitions from your code are retrieved as context for the model. They inform the review; they never excuse a rule violation.
Every push
Each push to a pull request is reviewed on its own. There is no comparison with the previous revision.
CLI, editor, MCP
“unslop scan” reviews your local changes against the merge-base and can apply suggested fixes. The VS Code extension shows findings as diagnostics. An MCP server offers the scan to coding agents. All three are built and not published yet.
Webhook security
Every GitHub webhook is verified by its HMAC-SHA256 signature.
Encryption
GitHub OAuth tokens are encrypted at rest with AES-256-GCM. API keys are stored as hashes.
Planned (concept)
Not built yet: failing the check when a revision raises findings over the previous one, and a second model vendor for the re-check.
Honest limits
Nothing catches every flaw in AI-written code, and neither do we. The model review reads TypeScript, JavaScript and Python only, and its results vary between runs.

FAQ

Frequently asked questions

Do you store or train on our source code?

We don’t train on it, and neither does Google: the Vertex AI service terms exclude using customer prompts and responses to train Google’s models. The review models run on the EU multi-region endpoint of Vertex AI, for which Google guarantees that ML processing stays inside EU member states. For a pull request we read the diff, the changed files and a small rule-based selection of further repository files (manifests, database schema, a service’s start file) from GitHub for the review, and we store the findings with the quoted lines, not the diff and not those files. A scan from the CLI or the MCP server uploads a diff and the same selection of files; the diff is stored with the scan, the selection only until the review has loaded it; before the upload, the client refuses any diff in which it detects a secret. The repository index is built from signatures, interfaces and type definitions rather than implementation logic. GitHub OAuth tokens are encrypted at rest with AES-256-GCM.

Will this slow down our CI/CD or block our merges?

It runs next to your CI, not inside it. The webhook returns as soon as the review is registered; the review then runs as a GitHub check run with a hard limit of 300 seconds. Only CRITICAL findings fail the check; warnings conclude as neutral and do not block a merge. Whether the check is required is your branch-protection setting.

Does my team need to learn another dashboard?

No. Findings appear as a check run and as review comments on the pull request. The CLI diffs your tracked changes against the merge-base, and the VS Code extension shows findings and applies suggested fixes in the editor; both are built and not published yet. The dashboard is for repositories, scan history, API keys and billing.

Is this just another AI reviewer?

Partly, and we say which part. 65 rules are checked by deterministic detectors, and those findings never pass through a model. The rest is a model review, and a finding from it can fail the check. That is why a second model re-checks the findings without seeing the first one’s reasoning, and why the review states when that re-check did not happen. Model calls run at temperature 0; results still vary somewhat between runs.

Which languages are covered?

The model review reads TypeScript, JavaScript and Python files. The deterministic pre-scan also covers Java, Go, Rust, C and C++ and common config files. A pull request without a TypeScript, JavaScript or Python file gets deterministic checks only, if it changes Java, Go, C, C++, PowerShell, Terraform, YAML or JSON files. No model reads it then: the check is titled “Deterministic checks only” and stays neutral unless a critical finding fails it. When a pull request mixes both, the summary names the file types no model read. Anything else, such as a docs-only pull request, still concludes as neutral with “Nothing to review”.

What happens when a review fails?

The check is closed, not left hanging. A failed review concludes as a failure with the reason in the check, for example “Review timed out”. A review that is not started (no subscription, quota used up, repository deactivated) posts a completed check with the conclusion “action required” that says why. If you made the check required, GitHub’s branch-protection overrides still apply.

Show the finding. Explain the verdict. Say what was skipped.

Unslop is in private beta. Sign-up is closed for now.