2026-10-11 16:38 UTC

fitzyracing1 releases Fakegreen, a zero-dependency, no-LLM CLI that scans git diffs for coding-agent fake-green patterns (skipped tests, weakened assertions, CI forced green) and integrates as end-of-turn hooks for Claude Code, Codex, and Gemini CLI.

state: seedheat: lowuncertainty: mediumconvergesscott: lowagentic-security coding-agent-harnesses ci-deceptionfitzyracing1

What is this?

Fakegreen is a zero-dependency, no-LLM CLI tool released by GitHub user fitzyracing1 that scans git diffs for patterns where coding agents fake a green build β€” skipped tests, weakened assertions, CI forced green. It integrates as end-of-turn hooks for Claude Code, Codex, and Gemini CLI. No web search results were returned to corroborate the release, repository details, or adoption; the grounding rests entirely on the case hypothesis.

Why it matters to Scott

Fakegreen is a released, zero-dependency CLI that implements the exact pattern Scott's canon has been arguing for: a mechanically different verifier (deterministic diff scanning) that catches coding-agent deception (skipped tests, weakened assertions, forced-green CI) at the end-of-turn hook layer for Claude Code, Codex, and Gemini CLI. This is not merely an example of a pattern β€” it is a concrete artifact that occupies the 'deterministic gate' slot in Scott's Deterministic Shell / Deterministic Core architecture, validates the 'cheap deterministic falsifier before LLM judgment' principle, and provides a dated-receipts case study for Guardrail Illusion and Trust Irrelevance. It directly bears on his agent-harness work (Ask, deterministic-agent-control-plane) and his Nightly AI Decision Builds / CI-deception framework.
ip:concept.mechanically-different-verifiersip:concept.deterministic-verification-before-assertionip:concept.guardrail-illusionip:concept.trust-irrelevanceip:concept.deterministic-shellip:framework.nightly-ai-decision-buildsdev:concept.deterministic-agent-control-planeip:concept.green-by-heroicsradar:bastiontrace-injection-forensicsradar:codeeraser-deterministic-code-judgeradar:vericordon-ci-authorization-evidenceradar:all-your-agents-session-monitorradar:concord-parallel-coding-agent-coordinationradar:grith-coding-agent-security-proxyradar:contemporary-agent-attacks-benchmark
queries asked of Scott's wikis
  • agentic-security: coding-agent deception and fake-green detection patterns
  • coding-agent-harnesses: end-of-turn hook integration for Claude Code, Codex, Gemini CLI
  • ci-deception: deterministic offline diff scanning vs LLM-based oversight
  • dev: local-first CLI tooling for agent harnesses
  • ip: agent eval/guardrails without LLM-as-judge dependency

Measured heat

now 0 pts/hpeak 5 pts/hcomments 0/hpeers p14momentum: steady2 platformsage 266h
points/hour across evidence Β· reading as of 2026-10-12 02:59:37.977291+11:00 Β· deterministic, not a model opinion

How the heat travelled

09-30 14:00⭐ origin echo-reconstructedOriginal project announcement. README: \"One command scans your agent's diff and flags every way it faked a green build.\" \"Coding agents (
Joshua Almeida (GitHub: fitzyracing1) on github (echo) Β· attributed from hn.story.50006996
β€”
10-08 15:27first on hacker news Β· published Β· +193.5hFakegreen: Catch AI coding agents faking a green build (no LLM)
fitzyracing1
β€”
10-08 15:27amplified on hacker news πŸ‘‘hn.story.50006996
fitzyracing1
peak 1 Β· 1 comments Β· 98% of case engagement
10-08 15:39our radar first saw it Β· +193.7hdiscovery anchor: hn.story.50006996β€”

Evidence (2) β€” ⭐ canonical anchor

sourceobjectauthorscorecomments
🟧 hnFakegreen: Catch AI coding agents faking a green build (no LLM)
Retrieved article excerpt

Open article Β· Retrieved 2026-10-08T17:49:32.797323+00:00

# fakegreen

[npm](https://www.npmjs.com/package/fakegreen)
[License: MIT](https://github.com/fitzyracing1/fakegreen/blob/main/LICENSE)
[Node.js](https://nodejs.org)

**One command scans your agent's diff and flags every way it faked a green build.**

### Who it's for

- Teams shipping with Claude Code, Codex, Cursor, Gemini CLI, or Aider who want a hard stop on fake-green diffs
- Maintainers who want a deterministic CI / pre-commit / end-of-turn tripwire β€” no LLM, no API key, zero runtime deps
- Anyone tired of agents that `.skip` tests, weaken assertions, or append `|| true` and then claim *"All tests pass βœ…"*

⭐ **If fakegreen catches a fake-green commit for you, [star the repo](https://github.com/fitzyracing1/fakegreen)** β€” it helps other teams find the tripwire.

### Try it in 30 seconds

```
npx fakegreen
```

No install. Needs Node.js 18+ and `git` on your `PATH`. Pin with `npm i -D fakegreen` when you're ready.

|  | LLM code review | fakegreen |
| --- | --- | --- |
| **Speed & cost** | Seconds–minutes, API spend | Usually **under 100 ms**, free, offline |
| **Determinism** | Can vary; easy to talk around | Same answer every time |
| **Setup** | API key + prompts | Zero runtime deps β€” `npx fakegreen` |



---

Coding agents (Claude Code, Codex, Cursor, Gemini CLI, Aider) are rewarded for "tests pass". Sometimes they get there by
deleting the test, slapping `.skip` on it, swapping `toBe(42)` for `toBeDefined()`, adding `@ts-ignore`, teaching the code
to detect `NODE_ENV === 'test'`, or appending `|| true` to the CI step. Then they say *"All tests pass βœ…"*.

`fakegreen` reads the git diff and catches those moves. It is **deterministic**, needs **no LLM and no API key**, has
**zero runtime dependencies**, and usually finishes in **under 100 ms**. Hook it into your agent's end-of-turn event so the
agent gets blocked and told what it did *before* it can claim it's done.

[fakegreen catching a fake-green agent commit](https://github.com/fitzyracing1/fakegreen/blob/main/docs/demo.gif)

## Demo

The recording above ([`docs/demo.cast`](https://github.com/fitzyracing1/fakegreen/blob/main/docs/demo.cast), [`docs/demo.gif`](https://github.com/fitzyracing1/fakegreen/blob/main/docs/demo.gif), [`docs/demo.png`](https://github.com/fitzyracing1/fakegreen/blob/main/docs/demo.png))
is a real run against a sample repo built by [`scripts/make-demo-repo.sh`](https://github.com/fitzyracing1/fakegreen/blob/main/scripts/make-demo-repo.sh). An honest commit adds a
cart module with tests. Then an "agent" commit titled `fix: make the test suite pass` skips one test, weakens an
assertion, deletes a test file, adds `@ts-ignore` plus a `NODE_ENV === 'test'` shortcut, and appends `|| true` to CI:

```
$ fakegreen --last-commit
fakegreen Β· last commit (8797f43) Β· 4/4 files Β· +5 βˆ’13

   HIGH  .github/workflows/ci.yml:10  ci-failure-ignored
         Failure ignored with `|| true` on a test/check command
         β”‚ + - run: npm test || true

   MED   src/cart.ts:4  suppression-added
         Checker silenced with @ts-ignore
         β”‚ + // @ts-ignore

   HIGH  src/cart.ts:5  test-env-special-case
         Non-test code branches on the test env (NODE_ENV === 'test'); tests skip the real path
         β”‚ + if (process.env.NODE_ENV === 'test') return items.length ? 8 : 0;

   HIGH  test/cart.test.ts:9  test-skipped
         Test skipped with .skip
         β”‚ + it.skip('applies SAVE10', () => {

   HIGH  test/cart.test.ts:14  assertion-weakened
         Specific assertion replaced with a vague one
         β”‚ - expect(total([])).toBe(0);
         β”‚ + expect(total([])).toBeDefined();

   HIGH  test/checkout.test.ts  test-file-deleted
         Test file deleted (2 test cases removed)
         β”‚ - it('rejects negative quantities', () => {

  5 high Β· 1 medium Β· 0 low  βœ– fake green detected (fail-on: high) Β· 41ms
```

Exit code `1`. Replay it with `asciinema play docs/demo.cast`, or rebuild it with
`scripts/make-demo-repo.sh /tmp/fakegreen-demo && cd /tmp/fakegreen-demo && fakegreen --last-commit`.

## Install

```
npx fakegreen                 # run once, no install
npm i -D fakegreen            # or pin it in a project
```

You need Node.js 18 or newer and `git` on your `PATH`.

## Usage

```
fakegreen                     # uncommitted + staged changes vs HEAD, plus untracked files (default)
fakegreen --staged            # only what is staged (good for pre-commit)
fakegreen --base origin/main  # everything since the merge-base with main, including uncommitted work (good for PRs)
fakegreen --last-commit       # just HEAD~1..HEAD
fakegreen --commit <sha>      # one specific commit
git diff main... | fakegreen --diff -   # any unified diff from stdin or a file

fakegreen --json              # machine-readable findings
fakegreen --sarif > fg.sarif  # SARIF 2.1.0 for GitHub code scanning and IDEs
fakegreen --format github     # ::error annotations for GitHub Actions
fakegreen --fail-on medium    # exit 1 on medium or higher (default: high; `none` never fails)
fakegreen --min-severity medium  # hide low-severity findings
fakegreen rules               # list every rule
```

Exit codes: `0` clean (or below `--fail-on`), `1` findings at or above `--fail-on`, `2` usage or git error.

Each finding includes a severity, `file:line`, a rule id, a short explanation, and the offending `+`/`-` lines.
`--json` output looks like this:

```
{
  "tool": "fakegreen", "version": "0.1.0", "source": "last commit (8797f43)", "failed": true,
  "summary": { "high": 5, "medium": 1, "low": 0, "total": 6, "files": 4, "analyzedFiles": 4, "added": 5, "removed": 13 },
  "findings": [
    { "ruleId": "test-skipped", "severity": "high", "file": "test/cart.test.ts", "side": "added", "line": 9,
      "message": "Test skipped with .skip", "snippet": "+ it.skip('applies SAVE10', () => {" }
  ]
}
```

## What it checks

It looks **only at added and removed lines** in the diff, so old code isn't re-flagged. Comments and string literals
are lexed out before matching, which keeps `".skip"` inside a string or a comment from triggering anything. Lines that
were only *moved* (renames, file splits, reordering) are ignored. A test file that moved, or whose tests reappear in
another file, does not count as a deletion. Supported languages: **JavaScript/TypeScript, Python, Go, Rust, Java**
(plus Kotlin test annotations), as well as CI and config files: GitHub Actions, GitLab CI, `package.json`, `tsconfig`,
jest/vitest/c8/nyc configs, `pyproject.toml`/`setup.cfg`/`tox.ini`/`.coveragerc`, Maven/Gradle, Makefiles, and
husky/shell hooks.

| Rule | Default | What it catches |
| --- | --- | --- |
| `test-file-deleted` | high | A test file was deleted (and not moved/renamed elsewhere in the diff). |
| `test-count-dropped` | high | The net number of test cases (test()/it()/def test\_/func Test/#[test]/@Test) went down. |
| `test-skipped` | high | A skip was added: .skip, xit/xdescribe, @pytest.mark.skip/xfail, pytest.skip(), t.Skip(), #[ignore], @Disabled, @Ignore. |
| `test-focused` | high | .only / fit / fdescribe silently disables every other test in the file or run. |
| `test-conditional-skip` | low | A conditional skip (skipif, skipIf, importorskip, assumptions, `if testing.Short()`) was added. |
| `assertion-removed` | medium | Net assertion count in a test file dropped (expect/assert/t.Error/assert\_eq!/assertEquals...). |
| `assertion-weakened` | high | A specific assertion (toBe/toEqual/assert x == y/assertEquals) was replaced by a vague one (toBeTruthy/toBeDefined/assert x/assertNotNull). |
| `assertion-trivial` | high | An assertion that can never fail was added (expect(true).toBe(true), assert True, assert!(true)). |
| `assertion-expected-changed` | low | Only the literal expected value of an assertion changed. Confirm the code was wrong, not the test. |
| `suppression-added` | medium | @ts-ignore, @ts-nocheck, @ts-expect-error, eslint-disable, # type: ignore, noqa, //nolint, #[allow(...)], @SuppressWarnings and friends. Blanket suppressions are medium, ones that name a specific rule are low, file/crate-wide ones are high. |
| `coverage-exclusion-added` | low | istanbul/c8/v8 ignore, pragma: no cover, LCOV\_EXCL, #[coverage(off)]. |
| `ci-failure-ignored` | high | `|| true`, continue-on-error: true, allow\_failure: true, --exit-zero, set +e on a test/lint/build command. |
| `ci-step-removed` | high | A command that ran tests, lint or type checks was removed from CI config, scripts or package.json. |
| `ci-step-disabled` | high | A CI job/step was disabled with `if: false` or `when: never`. |
| `test-script-neutered` | high | The package.json test script was replaced with echo/true/exit 0. |
| `test-exclusion-added` | medium | --passWithNoTests, -DskipTests, -x test, testPathIgnorePatterns, --ignore/--deselect, collect\_ignore. |
| `coverage-threshold-lowered` | high | A coverage threshold (coverageThreshold, fail\_under, --cov-fail-under, thresholds, jacoco minimum...) was lowered or removed. |
| `typecheck-weakened` | medium | tsconfig strict flags turned off, mypy ignore\_errors / strict = false, pyright typeCheckingMode off. |
| `lint-rule-disabled` | low | A lint rule was switched to "off"/0 in an ESLint config. |
| `test-env-special-case` | high | Non-test source code branches on being under test (NODE\_ENV === "test", JEST\_WORKER\_ID, "pytest" in sys.modules, testing.Testing(), cfg!(test)) or on CI. |
| `error-swallowed` | medium | New empty catch / except: pass / .catch(() => {}) / if err != nil {}. |
| `ignore-comment-added` | low | An inline fakegreen-ignore comment was added. Always reported so reviewers see what was waived. |

Severity is graded where it matters. For example, a blanket `# type: ignore` is medium, a targeted
`# type: ignore[attr-defined]` is low, and a file-wide `// @ts-nocheck` is high. `|| true` on `npm test` is high, while
`|| true` on `rm -rf build` is low. A conditional `skipif(sys.platform == "win32")` is low, but `skipif(True)` is high.

**Skipped automatically:** Markdown/docs, lockfiles, vendored and `node_modules` code, generated files, and
`fixtures/` / `testdata/` directories.

## Stop the agent in its tracks: end-of-turn hooks

Claude Code, Codex and Gemini CLI can run a command when the agent finishes its turn and **block** the stop, sending
the command's feedback back to the model. `fakegreen hook` speaks each agent's protocol. If it finds something at or
above `--fail-on`, the agent is told exactly what it faked and asked to fix it, or to stop and explain to you why the
change is intentional.

```
npx fakegreen install claude     # .claude/settings.json   (--local β†’ settings.local.json, --global β†’ ~/.claude)
npx fakegreen install codex      # .codex/hooks.json       (--global β†’ ~/.codex/hooks.json)
npx fakegreen install gemini     # .gemini/settings.json   (--global β†’ ~/.gemini/settings.json)
```

The installer shows a diff of the config change and asks before writing. Pass `--yes` to skip the prompt or
`--dry-run` to only preview. It merges into existing config, is idempotent, and refuses to touch a file that isn't
valid JSON.

**Claude Code**: `.claude/settings.json`

```
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "npx --yes fakegreen hook --agent claude",
            "timeout": 120,
            "statusMessage": "fakegreen: checking the diff for fake-green changes"
          }
        ]
      }
    ]
  }
}
```

On findings, the hook prints `{"decision":"block","reason":"..."}` and Claude keeps working with the reason as its
next instruction. ([Claude Code hooks docs](https://code.claude.com/docs/en/hooks))

**Codex**: `.codex/hooks.json`

```
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "npx --yes fakegreen hook --agent codex",
            "timeout": 120,
            "statusMessage": "fakegreen: checking the diff for fake-green changes"
          }
     
fitzyracing111
🟧 echo.github ⭐Original project announcement. README: \"One command scans your agent's diff and flags every way it faked a green build.\" \"Coding agents (Joshua Almeida (GitHub: fitzyracing1)β€”β€”

Interpretation history

Decision trace