# Anti-Slop: Oxlint Rules for Safer AI-Generated Code

AI coding agents generate code fast, but they also generate shortcuts: unsafe type assertions, functions with unknown return types, conditional empty object spreads, and other patterns that compile and pass basic tests but introduce subtle risks. These patterns are hard to catch in code review and easy to miss in a diff. Anti-slop is a set of custom [Oxlint](https://oxc.rs/docs/guide/usage/linter) rules, built by Dillon Mulroy, that catches them automatically.

The project is at [github.com/dmmulroy/anti-slop](https://github.com/dmmulroy/anti-slop) and **is meant to be vendored into your repository rather than installed as a fixed dependency**. You own the rules, can read them, and can modify them to match your team's standards.

<iframe class="aspect-video h-auto" width="100%" height="315" src="https://www.youtube.com/embed/mmrSYvYKD9g" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>



## What Oxlint is and why speed matters

Oxlint is part of the `oxc` (Oxidation Compiler) toolchain, written in Rust. It's benchmarked at 50–100x faster than ESLint.

![A slide from the Oxlint website showing its key features.](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/1b5aadb2-8b2d-4327-f877-a668d9112100/public =1920x1080)

For a human developer running a lint check manually, the difference between 200ms and 10ms is barely perceptible. For an AI agent running in a tight loop (generate → lint → fix → lint → commit), a slow linter creates real friction. Oxlint's speed keeps it from becoming a bottleneck in automated workflows.

## What anti-slop catches

The rules target two overlapping categories: low-evidence patterns (code that makes unverified assumptions) and low-signal patterns (code that's hard for static analysis or other developers to reason about).

![A screen showing the extensive list of anti-slop rules.](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/75042097-3113-4fad-5ac9-13ff92bef700/md2x =1920x1080)

The key rules:

- **`no-chained-type-assertions`**: Bans `as unknown as Type`. The creator calls this "type laundering": you're telling the compiler to discard all type evidence and trust you instead. Any runtime shape mismatch becomes an invisible bug.
- **`no-unknown-returns`**: Forces functions to have specific return types rather than `unknown` or `any`.
- **`no-unknown-parameters`**: Same enforcement for function parameters.
- **`no-conditional-empty-object-spread`**: Flags `...(condition ? { key: value } : {})`. This pattern is idiomatic but has edge cases around property merging that make it risky.
- **`no-known-value-widening`**: Prevents accidentally widening a precise type to a broader one.
- **`require-safety-comment-for-type-assertion`**: If you absolutely need a type assertion, this rule forces a `// SAFETY: ...` comment explaining why it's safe. This makes intentional assertions visible and auditable.

The concrete problem these rules address is common in AI-generated code. An agent fetching an API response and casting the result directly to an interface:

```typescript
interface Account {
  id: string;
  createdAt: Date;
}

const response = await fetch('/api/account/123');
const body = await response.json();
const account = body as unknown as Account; // anti-slop flags this
```

This compiles. It works until the API returns `createdAt` as a string rather than a `Date` object, at which point you have a runtime error with no type system warning and no clear location to trace it to.

## Why the error messages matter for agents

Traditional TypeScript errors can be cryptic. Anti-slop errors are descriptive enough to be actionable instructions.

![A comparison of a standard, hard-to-read TypeScript error with the clear feedback from anti-slop.](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/8f12ecef-5ed7-4eb7-9d42-9b779d5d3f00/public =1920x1080)

For `no-chained-type-assertions`, the error message reads something like: "This assertion chain discards type evidence. Keep the original precise type, or parse untrusted input at its boundary before narrowing it."

When that message gets fed back into an agent's context, it's not just a failure signal. It's a specific instruction: don't cast, parse and validate the data at the boundary. The agent can act on it directly rather than guessing what the error means.

## Installation

The fastest path is using the bundled agent skill, which handles copying the plugin, installing dependencies, merging the config, enabling rules, and validating the result:

```command
npx skills add dmmulroy/anti-slop --skill install-anti-slop
```

Then ask your coding agent to install or configure anti-slop in the current repository. The skill is designed to be invoked by the agent rather than run manually.

For manual setup: copy `src/` from the repository into your project at `tools/oxlint/anti-slop/`, install `oxlint` and `@oxlint/plugins`, and create the config file.

## Configuration

The real `oxlint.config.ts` structure uses `jsPlugins` and a `specifier` path, and importantly ignores all agent configuration directories so the rules don't fire inside `.claude/`, `.codex/`, and similar:

```typescript
[label oxlint.config.ts]
import { defineConfig } from "oxlint";

export default defineConfig({
  ignorePatterns: [
    ".agent/**", ".agents/**", ".claude/**", ".codex/**",
    ".continue/**", ".cursor/**", ".gemini/**", ".opencode/**",
    "tools/oxlint/anti-slop/**",
  ],
  jsPlugins: [
    { name: "anti-slop", specifier: "./tools/oxlint/anti-slop/index.ts" },
  ],
  rules: {
    "anti-slop/no-chained-type-assertions": "error",
    "anti-slop/no-conditional-empty-object-spread": "error",
    "anti-slop/no-known-value-widening": "error",
    "anti-slop/no-module-mocking": "error",
    "anti-slop/no-object-parameters": "error",
    "anti-slop/no-reflect-apply": "error",
    "anti-slop/no-reflect-get": "error",
    "anti-slop/no-runtime-typeof": "error",
    "anti-slop/no-shape-in-symbol-names": "error",
    "anti-slop/no-unknown-parameters": "error",
    "anti-slop/no-unknown-returns": "error",
    "anti-slop/no-unknown-type-aliases": "error",
    // "anti-slop/no-unsafe-type-assertion": "error", // opt-in
  },
});
```

For repositories using Effect-TS, there's an opt-in Effect rule group that adds additional rules appropriate for that ecosystem.

Add the lint script to `package.json`:

```json
[label package.json]
{
  "scripts": {
    "lint": "oxlint src"
  }
}
```

![The `package.json` file with the `lint` script configured.](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/b53cfbfc-f3f5-413f-f004-ca1a18742400/lg1x =1920x1080)

## Seeing it in action

With the config in place, running `npm run lint` on code with a chained type assertion produces a detailed error:

![A close-up of the highly descriptive error message provided by an anti-slop rule in the code editor.](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/43c63014-3f5d-4872-d783-ca79ad6ac800/md1x =1920x1080)

The corrected version of the `getUser` example uses a Zod schema to validate the API response at the boundary rather than asserting the type:

```typescript
[label api-client.ts]
import { z } from 'zod';

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
});

export type User = z.infer<typeof UserSchema>;

export async function getUser(id: string, token?: string): Promise<User> {
  const headers: Record<string, string> = {};

  if (token) {
    headers['Authorization'] = `Bearer ${token}`;
  }

  const response = await fetch(`/api/users/${id}`, { headers });

  if (!response.ok) {
    throw new Error('Failed to fetch user');
  }

  const data = await response.json();
  return UserSchema.parse(data); // validates instead of asserting
}
```

This passes all anti-slop rules. The `UserSchema.parse()` call is high-evidence: it confirms at runtime that the data matches the expected shape and throws with a clear error if it doesn't, rather than silently passing through a mismatched type.

## The agent feedback loop

The practical value in agentic workflows is the loop: **agent generates code, lint runs, descriptive error output goes back into agent context, agent fixes the specific problem, lint runs again**. The error messages are informative enough to close that loop without human intervention in most cases.

For `no-chained-type-assertions`, the fix is almost always the same: move to runtime validation with a library like Zod or Valibot. The error message tells the agent exactly that. For `no-unknown-returns`, the fix is to add a specific return type annotation. Again, the error message makes the action clear.

**This is different from ESLint errors like "unexpected token" or TypeScript errors that require reading the type system's internal state**. Anti-slop errors are written to be acted on, which makes them useful beyond just blocking CI.

The vendoring philosophy reinforces this: you own the rules in your repository, you can read what each one does, and you can adjust severity or disable rules that don't fit your codebase. It's not a black-box dependency; it's a readable set of TypeScript files sitting in `tools/oxlint/anti-slop/`.