Space Bunny Alpha: The Free Stealth Model That Found Six Real Bugs in My Codebase

Space Bunny Alpha: The Free Stealth Model That Found Six Real Bugs in My Codebase

Space Bunny Alpha: The Free Stealth Model That Found Six Real Bugs in My Codebase

On 23 September 2026, an unnamed model called Space Bunny Alpha appeared on OpenRouter with no vendor, no model card and a $0 price tag. A few days later I repointed my assistant at it and handed it the job I normally avoid: a full audit of the codebase behind my own financial planning app. This post is being drafted by that model.

That isn’t a stunt. It’s the most honest framing I can give, because the interesting part isn’t that a free model exists. It’s what happened when I pointed it at code that has to be right — and what it cost to be sure it actually was.

What Space Bunny Alpha actually is

The published facts are short because there isn’t much to publish. The model identifier is stealth/space-bunny-alpha. OpenRouter states plainly that it is developed and operated by a third-party provider who has chosen to stay anonymous, that OpenRouter routes requests to it and is not its developer, owner or provider, and that prompts and completions may be retained by that provider but are not used for training. Everything else is governed by OpenRouter’s Stealth Model Terms.

  • Released: 23 September 2026, as one of three stealth models on the platform
  • Context window: 1,000,000 tokens
  • Price: $0 per million input tokens, $0 per million output tokens, cache reads included
  • Input: native multimodal — text, image, video and audio
  • Reasoning: adjustable effort, not fixed on or off
  • Providers: exactly one. OpenRouter forwards every request straight to it, so there is no routing decision and no failover

Community testers report a 524,288-token maximum output — roughly four times the 128,000-token ceiling Claude allows — which would make single-shot generation of an entire application plausible rather than theoretical. OpenRouter doesn’t publish that figure, so treat it as reported rather than confirmed.

As for who built it: within hours of launch, developers started matching its tokenizer byte-pair splits, its out-of-memory error payloads and its linguistic defaults against known fingerprints, and kept landing on the same lab. The evidence is circumstantial but unusually consistent, and nobody involved has confirmed a thing. Anonymous is the point of a stealth preview, so the guessing is the game.

The real signal is adoption. The apps sending the most traffic to it are Cline, Command Code, Kilo Code, Hermes Agent and Claude Code — all agentic coding tools that live inside a loop, reading a repository, editing files and running commands. Whatever this model is, the market has decided that’s what it’s for.

The numbers that actually mattered

Performance data on the OpenRouter model page, averaged over the last week:

  • Throughput: 87 tokens/sec at P50, 194 at P99
  • Latency to first response: 1.31s at P50, 9.22s at P99
  • End-to-end: 7.75s at P50, 69.81s at P95, 215s at P99
  • Cache hit rate: 95%
  • Tool call error rate: 3.09%
  • Availability, last 3 days: 98.88%, with 100% uptime

Two of those deserve a comment. The 95% cache hit rate is what makes a million-token window affordable in practice — in an agent loop most of the context is a re-read, not new input, and a model that caches aggressively is the difference between a usable free tier and a rate-limited toy.

The 3.09% tool call error rate is the number I’d actually watch. That’s roughly one malformed tool call every 32 attempts. Interactively you never notice it. In a long autonomous loop it means supervising the work rather than trusting it, because a bad tool call doesn’t fail loudly — it just quietly sends the next step somewhere else.

And here’s the honest caveat that most posts about this model skip: nothing has benchmarked it independently yet. Command Code lists its intelligence, coding and speed scores as “not yet scored”. Benchable shows no results. So every capability claim currently circulating — including the ones quoting impressive percentages against models I can’t verify exist — is unverified marketing or guesswork. The only measured facts are the performance telemetry and the price.

The codebase: fifteen screens, four engines, and no error messages

The app is one I built and run for my own family. You feed it a balance sheet, a salary, an expense run-rate and a set of life goals, and it projects your net worth all the way to retirement, runs a probability analysis on whether the plan survives a decent slice of market history, and then works out how much you can genuinely spend each year of retirement without running out — and without leaving nothing behind.

Under the hood it’s a full-stack TypeScript application: a file-based router with server functions, React on the front end, an ORM over a managed Postgres instance, authentication, and a charting layer on top. Roughly fifteen screens, including a four-step onboarding flow, the main dashboard, settings, and separate pages for investments, insurance, legacy and the die-with-zero planner — plus a small education section with interactive explainers for CPF LIFE deferral, universal life policy stress-testing and CareShield supplements.

And then there are the four calculation engines sitting underneath all of that, which are the reason this codebase is worth an audit at all:

  • The retirement simulator. 10,000 Monte Carlo paths, where every simulated year draws one real calendar year from the pool of actual S&P 500 annual returns, 1928 to 2025, index-aligned so the equity leg and the forecasting leg always share the same year. Sitting next to it, a projection engine that blends growth rates across savings, investment and retirement account classes, and a milestone system that injects cash flows at specific ages — tuition, a property purchase, a bequest.
  • The universal life policy stress-tester. A policy simulator with a $2.5M death benefit that calibrates the non-guaranteed leg against real historical index data rather than a marketing illustration.
  • CPF LIFE deferral. Working out what pushing your CPF LIFE payout out by a decade actually costs you in monthly retirement income, with the compounding assumptions laid out where you can argue with them.
  • Die with Zero. The retirement spending engine. It walks year by year, applies a health-decline factor to your spending capacity, targets a legacy amount, and works backwards to the largest sustainable annual spend that still ends on the number you asked for.

That is the part that makes this hard, and it has nothing to do with the size of the source. Almost every number this app produces is a silent failure. A misplaced multiplication by 100 doesn’t throw an exception. It renders a confident, plausible retirement plan on a chart, and the person reading it — me — has no way to know it’s wrong. A health-decline rate applied to the wrong quantity doesn’t crash; it makes your retirement look better than it is. A fabricated compounding assumption doesn’t warn you; it just fills a column.

TypeScript can’t help with any of that. Neither can linting. Neither can four schema migrations and a JSONB column carrying a dozen-plus fields that the app reads and writes but that no type describes. The only defence is a domain invariant you can actually execute — and until this session, most of the invariants in this codebase lived only in my head.

What it found

Six correctness bugs in a calculation engine it had never seen, all of the same species: a number that was quietly the wrong number.

1. Health decline was applied to the wrong quantity

The idea behind “die with zero” is that your spending capacity declines as you age while your enjoyment capacity does not. The code had the decline factor multiplying the count of years you’d enjoy, so raising the decline rate made retirement look longer instead of shorter. It now multiplies spending from the health-decline start age, decaying year on year and floored at 20% of baseline. A 1% annual decline from 65 now reads 1.00 at 65, 0.90 at 75, 0.80 at 85, 0.75 at 90.

2. The final years targeted the wrong balance

Once health decline kicked in, the engine aimed its spending at whatever the terminal balance happened to be. Set a legacy target and the optimiser could spend the inheritance down while still reporting it as unmet. The last leg now aims at the legacy target explicitly, which is what “die with zero, but leave this behind” is supposed to mean.

3. A negative balance was doing two jobs

The terminal balance was simultaneously the solvency signal and the legacy result, so “you run out of money” and “you fall short of the legacy” were both encoded as a number you had to interpret yourself. They’re separate fields now, with an explicit shortfall age. An unaffordable plan — $10,000 of assets against $200,000 of annual expenses — now reports a shortfall at age 45 instead of printing a negative balance and hoping you read it correctly.

4. A unit mismatch, in two places at once

The health decline rate is stored in percentage points, so 1 means 1%. The engine divided by 100 correctly. The summary table on the page multiplied by 100 again and skipped the 20% floor while it was at it, so the table and the chart above it disagreed with each other. This is the classic failure mode of a units bug: both numbers look fine in isolation, and only the disagreement gives them away.

5. A fabricated 3% compounding

The per-year breakdown had been growing the “memory” value at 3% a year, presented as if it were a return. It wasn’t in the model. It was invented to make the column look less flat. It’s gone, because a financial table has no business showing a growth rate the engine doesn’t actually apply.

6. A mislabelled metric

experienceYears was simply retirementAge - currentAge, labelled as though it meant years of full enjoyment capacity. It now reports when enjoyment actually hits the floor: 41 years at a 1% annual decline starting at 65, 30 years at 5% starting at 70, 46 years with no decline at all.

Underneath those, the page had been recomputing health factors and drawdowns itself instead of reading the rows the engine had already produced. That duplication is why the table and the chart drifted apart in the first place. The UI now reads the engine, so there’s one source of truth.

The same pass turned up the dead weight:

  • Two engine defaults for health decline (start 65, rate 1) that were unreachable, because the page always supplies both
  • Two exported currency and percentage formatters nothing imported
  • An authentication middleware module — five lines, never imported, in an app that doesn’t register global middleware at all
  • Two derived result fields and the local variable that fed them
  • A 10,000-element terminal-values array computed on every Monte Carlo run and carried through React state by the dashboard, used by nothing. The separate universal life page reads its own equivalent array, so that one stayed
  • Five separate copies of as Record<string, any> over the same JSONB column, replaced by one typed view in a shared module

That last one earned its keep immediately. Compiling the new type surfaced three fields — cpfTopUps, numKids, targetAmount — that the app had been writing and reading since the beginning and that existed in no schema, no type and no documentation. Three of the five any casts weren’t hiding type errors at all. They were hiding a fourth kind of problem: not a mismatch, just an undocumented contract nobody had written down.

The part that mattered more than the findings

It wrote the test.

Not a test framework, not a mock, not a suggestion to consider adding tests later — a 79-line script that runs the calculation engine directly and asserts the things that must never be true. It sweeps the legacy target from $0 to $3,000,000 in steps and asserts the floor always holds, that the target is always reported as met, and that the terminal balance never lands below the target. It asserts that an unaffordable plan surfaces a shortfall age instead of a negative balance. It asserts the health factor reads 1.00 at 65, 0.90 at 75, 0.80 at 85 and 0.75 at 90 against a floor of 0.20.

It runs in about a second and needs no browser. Before it existed, every one of those six bugs was a plausible number on a chart. After it, they were assertions.

Which is the actual lesson, and it isn’t really about the model. A model is only as good as the oracle you hand it. The free price tag changed the economics — I could afford four passes over the same code instead of rationing one careful attempt — but what made the work safe was writing the thing that proves it’s right, and I would have needed to do that with any model I paid for too.

Final tally: 13 files, 326 insertions, 149 deletions, split across two commits so a toolchain dependency didn’t get tangled up with the fix. TypeScript reported zero errors, the production build went through clean, and the invariant harness passed before anything was pushed.

What you actually get, plainly

  • The economics stop being a reason to say no. For scale, Command Code prices a full-repository agent run — 900K tokens in, 45K out — at around US$0.49 on Claude Haiku 4.5. Mine cost nothing at all. Any task you’d previously skipped as “not worth the tokens” is now free, and for a solo developer that changes which work is worth doing, not just which work is affordable.
  • 1M context is the real unlock, not the price. No chunking, no “summarise this file first”, no losing an invariant that lives in one file while editing another. The whole application fitted in one window with room to spare, which meant the model could reason about the calculation engine and the page that renders it in the same pass — the exact relationship where the bugs lived. This is the part that would have mattered even if the model cost money.
  • Adjustable reasoning effort earns its keep on hard maths. Crank it up for the projection engine, dial it down for a dead-code sweep. On a $0 model you have no reason to pick one setting for everything.
  • Native multimodal input shortens the loop. A screenshot of the rendered dashboard or a pasted terminal error becomes part of the prompt, instead of a transcription exercise where the interesting detail gets lost in the retelling.
  • It plugs into what you already have. Standard tool calling and structured output over an OpenAI-compatible surface, so it dropped into an existing agent setup with no glue code.

Where I wouldn’t trust it yet

  • No vendor, no accountability. No changelog, no support, no SLA, no published model card. If it regresses on a Tuesday there is nobody to tell, and the identifier can vanish when the preview ends.
  • One provider. OpenRouter states it forwards every request directly, so there’s no healthy second upstream to fail over to. 98.88% availability over three days is a fine number; it just isn’t one I’d put a deadline behind.
  • A 3.09% tool call error rate. Unnoticeable in conversation, noisy across a hundred-step agent run. Plan to supervise.
  • No independent benchmarks. Not one credible third-party number yet, in either direction. Treat every score you read about it this week as unverified.
  • Anonymity cuts both ways. I can see what it did this time. I can’t see what it won’t do.
  • Read the retention terms before you point anything sensitive at it. “Not used for training” is not the same promise as “not stored”. For a personal app full of my own made-up numbers it’s irrelevant. For a client codebase, it is the whole decision.

And the plainest caveat of all: it produced six plausible-looking financial bugs in the first place. Fast and free is not the same as correct. The compiler caught the type errors. Only the domain harness caught the rest.

How I’d actually use it

  • Good fit: repository-wide audits, dead-code sweeps, mechanical refactors spread across many files, first-pass review of code you can compile and test, and any job you’d otherwise skip because of the token bill.
  • Poor fit: the last mile. Anything where a wrong answer costs real money or leaks something you can’t take back keeps a known-good paid model and a human eye on it.
  • Non-negotiable: make it write the check. Whatever the oracle is — a compiler, a test suite, an invariant sweep, a figure you verify by hand against a spreadsheet — if you don’t have one, a free model will hand you a wrong number at no cost whatsoever, and the absence of a bill is exactly what stops you looking twice.

The economics here are temporary by design. Free stealth previews are a sampling exercise, not a business model, and when the window closes the model may not even still exist under that name. So treat it the way you’d treat any free thing that’s about to disappear: take the whole-repository pass while it’s free, keep the oracle, and don’t let a $0 price tag convince you that nothing you got back needed verifying. Free inference doesn’t change how much verification is worth. It changes how many times you can afford to check.

Leave a Reply