scan-reward-hacking · v1.1.0 · 2026-04-29 · sha256 2361f29b90335436
scan-reward-hacking v1.1.0A
Immutable. This exact content is served forever at /api/v1/blob/2361f29b90335436.
---
name: scan-reward-hacking
description: "Scan TypeScript code for reward hacking patterns — shortcuts that make linters pass without actually fixing type safety. This skill has a comprehensive checklist of 8 forbidden patterns with severity tuning (libraries vs apps) that you cannot reliably check on your own. **ALWAYS consult this skill when** the user says 'scan for hacks', 'check for type cheats', 'reward hacking', 'verify no shortcuts', wants to check for `as any`, `as unknown as`, `@ts-ignore`, non-null assertions (`value!`), `forEach(async`, or void tricks after fixing TypeScript errors. Also use after /fix-typescript completes, or when verifying TypeScript changes before marking work done. Accepts optional file/directory arguments to scope the scan."
disable-model-invocation: false
allowed-tools: Bash, Read, Grep, Glob
argument-hint: "[files-or-directories]"
version: 1.1.0
---
# Scan for Reward Hacking Patterns
You are scanning for "reward hacking" patterns — code that makes linters pass without actually
fixing type safety issues. This is a verification step that MUST be run before marking TypeScript
work complete.
## What to Scan
**If `$ARGUMENTS` specifies files or directories**, scan those paths only.
**Otherwise**, detect scan paths automatically:
1. Use Glob to find which of these directories exist: `src/`, `apps/`, `packages/`, `lib/`
2. Scan all that exist
## Severity Tuning
Severity levels adjust based on project context:
| Pattern | Libraries/Packages (`packages/`) | Applications (`apps/`, `src/`) |
|---------|----------------------------------|-------------------------------|
| `as any` | **CRITICAL** | HIGH |
| `as unknown as` | **HIGH** | HIGH |
| `@ts-ignore` | **CRITICAL** | HIGH |
| Non-null assertion (`!`) | **HIGH** | MEDIUM |
Libraries/packages are stricter because they export types consumed by other code. Determine context
from the file path — files under `packages/` use library severity, everything else uses app severity.
## Forbidden Patterns to Detect
Use the **Grep tool** (not bash grep) to search for each pattern. Use glob `*.{ts,tsx}` to filter
to TypeScript files only. Run all searches and report ALL matches:
### 1. Undocumented Double-Casts (HIGH)
Pattern: `as unknown as`
### 2. Direct Any Casts (HIGH / CRITICAL in libraries)
Pattern: `as any`
### 3. Void Tricks (CRITICAL)
Patterns: `void (0` and `void _`
### 4. Underscore-Prefixed Local Variables (MEDIUM)
Patterns: `const _[a-zA-Z]` and `let _[a-zA-Z]`
Evaluate context: function parameters are acceptable, local variables are not.
### 5. TypeScript Directive Comments (HIGH / CRITICAL in libraries)
Patterns: `@ts-ignore` and `@ts-expect-error`
### 6. Non-Null Assertions Without Runtime Guard (HIGH / MEDIUM in apps)
Pattern: `\w+!\.` and `\w+!\[` and `\w+!;`
These match `value!.property`, `value![index]`, and `value!;` patterns.
**ACCEPTABLE** (has a preceding runtime guard):
```typescript
if (user != null) {
return user!.name; // Guard exists above
}
```
**NOT ACCEPTABLE** (no runtime check):
```typescript
const name = user!.name; // Could be null at runtime
```
When evaluating matches, read surrounding lines (use Grep with `-B 3` context) to check for
a preceding null/undefined guard (`!= null`, `!== null`, `!== undefined`, `!= undefined`,
truthiness check, or `if` guard).
### 7. Async Correctness Issues (HIGH)
**7a. `forEach` with async callback:**
Pattern: `\.forEach\(async`
This silently drops promise results. Always use `for...of` or `Promise.all(array.map(...))`.
**NEVER ACCEPTABLE**:
```typescript
items.forEach(async (item) => { // Promises silently dropped
await processItem(item);
});
```
**7b. Unhandled async function calls:**
Pattern: lines that call an async function without `await`, `return`, `void`, or `.then()`.
This is harder to detect via pattern matching alone. Flag `forEach(async` reliably; for other
cases, note them as informational if spotted during the scan.
### 8. Exported Unused Types (LOW — informational)
Patterns: `^export type [A-Z]` and `^export interface [A-Z]`
These are informational only and do not affect the verdict.
## How to Evaluate Matches
### `as unknown as` — Check for Documentation
**ACCEPTABLE** (has required documentation):
```typescript
// LIBRARY TYPE LIMITATION: The thirdPartyWrapper() function returns a type
// that TypeScript can't verify implements the expected interface.
// Verified at runtime that the object has the required methods.
// TODO: Remove when library updates types (tracked in TICKET-XXX)
const wrapped = thirdPartyResult as unknown as ExpectedInterface;
```
**NOT ACCEPTABLE** (no documentation):
```typescript
const campaigns = result as unknown as Campaign[];
```
### `as any` — Almost Always Wrong
**ACCEPTABLE** (rare — only in test mocks):
```typescript
// In test file only
const mockDb = { query: vi.fn() } as any as Database;
```
**NOT ACCEPTABLE** (production code):
```typescript
const data = response.data as any;
```
### `void` Patterns — Always Wrong
**NEVER ACCEPTABLE**:
```typescript
void (0 as unknown as _Type); // Lint suppression trick
void _schemaCheck; // Unused variable suppression
```
### Underscore Variables — Context Matters
**ACCEPTABLE** (function parameters):
```typescript
function handleEvent(_event: Event, data: Data) {
return process(data);
}
```
**NOT ACCEPTABLE** (local variables):
```typescript
const _user = useUser(); // Keep for future use <- DELETE THIS
```
### `@ts-ignore` / `@ts-expect-error`
**ACCEPTABLE** (rare — with documented reason and tracking ticket):
```typescript
// @ts-expect-error — library types are wrong, fixed in next release (PROJ-456)
const result = brokenLib.doThing();
```
**NOT ACCEPTABLE** (no explanation):
```typescript
// @ts-ignore
const data = thing.stuff;
```
### Non-Null Assertions
**ACCEPTABLE** (runtime guard exists):
```typescript
if (map.has(key)) {
return map.get(key)!; // Safe — has() guarantees existence
}
```
**NOT ACCEPTABLE** (no guard):
```typescript
return this.user!.email; // Could crash at runtime
```
## Output Format
Present findings in this format:
```markdown
## Reward Hacking Scan Results
**Scan scope**: {paths scanned}
**Severity mode**: {library | app | mixed}
### CRITICAL (Must Fix Immediately)
- `file.ts:123` - `void (0 as unknown as Type)` - Lint suppression trick
- `packages/core/src/index.ts:45` - `as any` in library code
### HIGH SEVERITY (Must Fix Before Merge)
- `file.ts:456` - `as unknown as Campaign[]` - Missing documentation
- `file.ts:789` - `as any` in production code
- `file.ts:55` - `user!.name` - No runtime guard
- `file.ts:100` - `.forEach(async` - Silently drops promises
### MEDIUM SEVERITY (Should Fix)
- `file.ts:101` - `const _user = ...` - Unused local variable
- `apps/web/src/page.ts:30` - `item!.id` - No runtime guard (app code)
### ACCEPTABLE (No Action Needed)
- `file.test.ts:50` - `as any` in test mock
- `file.ts:200` - `as unknown as` with full documentation
- `file.ts:300` - `map.get(key)!` after `map.has(key)` guard
### Summary
- Critical: X issues
- High: Y issues
- Medium: Z issues
- Total requiring action: X + Y + Z
```
## Verdict
**PASS**: No forbidden patterns found, or all patterns are properly documented/in tests.
**FAIL**: Forbidden patterns found that require fixes before work can be considered complete.
## If FAIL
List the specific fixes needed:
```markdown
## Required Fixes
1. `apps/api/src/services/UserService.ts:243`
- Current: `as unknown as CreateUserRequest`
- Fix: Fix the query return type or add Zod validation at the boundary
2. `apps/web/src/pages/Dashboard.tsx:117`
- Current: `const _user = useUser();`
- Fix: Delete the line entirely
3. `apps/web/src/pages/Dashboard.tsx:55`
- Current: `items.forEach(async (item) => { ... })`
- Fix: Use `for (const item of items) { await ... }` or `await Promise.all(items.map(...))`
4. `packages/core/src/client.ts:89`
- Current: `this.config!.apiKey`
- Fix: Add null check or use optional chaining (`this.config?.apiKey`)
```
The agent must address ALL issues before marking their work complete.