Guren DocsGuren Docs

October 26, 2026

Guren and Void, same tickets: where a bundled check changes the outcome

We gave Claude Code the same five feature tickets on Guren and on Void. Pass rates matched and Guren cost about 1.4× as much, but when we removed one authorization check from both apps, Guren's audit, delivered by its Stop hook, decided the outcome: 18/18 against 6/18 on Haiku 5.5.

In October 2026 VoidZero open-sourced Void, a full-stack framework that puts Vite, Hono and Drizzle together and deploys to Cloudflare. Guren is built on Hono, Drizzle and Inertia (React), and Void also ships help for coding agents: docs for the installed version inside the package, an AGENTS.md and skills.

With that much overlap, we wanted to see how the two compare when an agent does the work. We gave Claude Code the same tickets on both and measured. Some of what we found is already fixed in Guren v2.31.0, covered at the end.

How the two differ

Void Guren
Built on Vite, Hono, Drizzle Bun, Hono, Drizzle, Inertia
Routing By file location (pages/, routes/) Named routes in a routes file (Laravel-style)
Page data A loader's return value becomes props A controller passes props through Inertia
UI React, Vue, Svelte, Solid React
Deploy Cloudflare first (void deploy); Node.js, Bun and Deno builds with fewer features Cloudflare Workers, Vercel, AWS Lambda, Docker
For agents Bundled docs, AGENTS.md, skills CLAUDE.md, rules, skills, hooks, check commands

Void's loader draws on Inertia, so server data turning into page props works much as it does in Guren. Routing is where they part: Void derives routes from where files sit, and Guren names them in a routes file. Void's void deploy also creates resources, applies migrations and deploys in one command, according to its docs. We did not run a deploy in this comparison.

Type-checking cost

Void generates its route types into .void/ (type safety docs) instead of inferring them through a Hono RPC chain, which gets slower as routes grow. Guren generates route types too. So we registered 500 identical APIs and measured with TypeScript 7.0.2 (increase over zero APIs, median of three runs).

500 APIs instantiations check time
Guren +396k +0.33s
Void 1.1.11 +351k +0.22s
Hono RPC (one chain) +4,872k +2.27s

Guren's instantiations are 1.13× Void's, the same order, and about a twelfth of a single Hono RPC chain. We grew APIs only, not pages.

Check time is about 1.5× Void's. The cause is one routes file registering all 500 routes, where the Zod schemas for input validation pile up. The full benchmark has the breakdown and the fixes we plan.

Same tickets, two frameworks

Setup

Both sides have the same small blog: login, posts and comments. We wrote the Void app ourselves from its docs; it is about a third the size of the Guren app. Claude Code then took five feature tickets on each:

  • tags on posts
  • comment deletion rights, and hiding a comment after reports
  • post revision history
  • a JSON API with token authentication
  • scheduled publishing

A run passes when a shared HTTP test suite and the app's typecheck both pass. Each ticket ran three times, with each framework's own agent setup.

Results

On Claude Sonnet 5.5 and Claude Haiku 5.5, every run on both frameworks passed. These five tickets did not separate them.

For cost we ran Sonnet 5.5 fifteen times per side. The Guren app is the v2.31.0 template. Cost is API-equivalent, computed from token counts.

Guren v2.31.0 Void 1.1.11
Passed 15/15 15/15
Cost per run (median) $0.347 $0.254
Total, 15 runs $6.09 $3.85

Guren costs about 1.37× Void at the median and 1.58× in total. Most of the gap in the total comes from the JSON API ticket.

Guren's agents read more: about 7k more tokens at session start and about 11k more per model call, because there are more files to read (controllers, validators, resources). We did not split the cost difference by cause.

Our first measurement showed 2.2×. Only the Guren prompt asked for tests, and the Guren pages were larger. With both equal the gap was 1.6×; with lighter agent guidance and the current template it is 1.37×.

A defect the ticket never mentions

Ordinary tickets showed no difference, so we planted a defect. In both apps we removed the one line that checks whether the signed-in user owns the post. The ticket does not mention it. The question is whether the agent notices while it works on the ticket, and fixes it.

Removed authorization check Model Guren Void
Delete a post Haiku 5.5 9/9 0/9
Update a post Haiku 5.5 9/9 6/9
Update a post Sonnet 5.5 9/9 9/9
Post edit form (GET) Haiku 5.5 0/6 1/6

On Haiku 5.5, across delete and update, Guren fixed 18 of 18 and Void 6 of 18.

What guren audit does

The difference comes from guren audit, a check command that ships with Guren. It reads the code for gaps such as mutating routes without validation, authentication or authorization, raw SQL, and hard-coded secrets.

Here is the update action from the Guren template (abridged). this.authorize() asks the policy whether the signed-in user may update this post.

async update(): Promise<Response> {
  const { id } = this.validateParams(PostIdParamSchema)
  await this.authorize('update', [Post, await Post.findOrFail(id)])
  const { body: data } = this.validated('posts.update')
  await Post.update({ id }, data)
  return this.redirect('/posts/' + id)
}

Remove the this.authorize() line and run guren audit, and it warns (shortened):

bunx guren audit
WARN [A01] PUT /posts/:id: PostController.update (app/Http/Controllers/PostController.ts:53)
  references Post (PostPolicy) on a mutating route, and neither the middleware chain
  nor the action body shows an authorization check.
  → Call await this.authorize('<ability>', [Post, post]) in PostController.update before the write

The route still has auth middleware, so guests are turned away, but any signed-in user can now rewrite anyone's post. The audit finds this from the route and controller together: a model with a policy is written to and nothing authorizes it.

A warning does not fail anything, so an agent only sees it if it runs guren audit itself. In v2.31.0 the agent harness closes that gap:

  1. The agent implements the ticket.
  2. It tries to finish. The Stop hook runs guren audit on the files it changed.
  3. The audit reports a missing authorization check on PUT /posts/:id.
  4. The hook passes the warning to the agent and blocks the stop once.
  5. The agent restores this.authorize() and tries to finish again. No warnings, so it stops.

In the Guren runs, this is what happened: the warning reached the agent, and its next edit put this.authorize() back.

Where there was no difference

  • The edit form's authorization was not checked by Guren at the time, and neither side's agents fixed it. v2.31.0 adds that check; we have not measured it since.
  • Sonnet 5.5 noticed the missing update check on its own and fixed it in every run on both frameworks.

Sending a check's result to the agent makes the defects that check reads get fixed. Where no check exists, Guren's results did not change.

We ran this on an app from before v2.31.0, with the agent setup that later shipped. The removed check sits in the same place in both apps, and we wrote the selection rules before any run. Still, we picked a defect Guren has a check for, so the comparison favours Guren. The selection rules are public.

What we learned about the measurement

A small mismatch moves the numbers a lot. Equalizing the prompt and page size alone took the gap from 2.2× to 1.6×.

Easy tickets hide differences. We later re-graded 90 saved answers against checks the tests skip (input validation and the like); one failed. Seeing a difference took a planted defect.

Decide how defects are chosen before running anything. Choosing after seeing results tilts the comparison toward your own framework.

What shipped in Guren v2.31.0

Several of the gaps we found are fixed in v2.31.0.

One-command deploy to Cloudflare

guren cloudflare:deploy builds, creates D1, KV and R2 resources, applies migrations and deploys.

bunx guren cloudflare:init
bunx guren cloudflare:deploy --plan
bunx guren cloudflare:deploy

--plan shows the resources it would create, pending migrations and missing secrets, and writes nothing to the account. We ran it end to end on a real account on the Workers Free plan.

A guard against destructive migrations

guren db:migrate stops before applying a migration that drops a table or column. To let it through, add a comment with the reason, or run with --allow-destructive.

-- guren:allow-destructive: legacy column already migrated
ALTER TABLE posts DROP COLUMN legacy_body;

If a deploy script runs db:migrate, check it before upgrading.

Leaner agent guidance

The Stop hook now passes guren audit warnings to the agent, as described above. Rules are split into a short core and a reference read on demand, so reading a controller or model attaches about 2.5–4k tokens of rules instead of 7–8k.

Summary

  • Type-checking cost with 500 APIs is on par with Void.
  • On five feature tickets, pass rates did not differ on either model.
  • Guren cost about 1.37× Void at the median, 1.58× in total.
  • With an authorization check removed, Guren fixed 18/18 to Void's 6/18 on Haiku 5.5 where Guren has a check for the defect.
  • Where there was no check, results matched, and Sonnet 5.5 found the update defect by itself.

If you want Cloudflare resources and deploys handled for you, Void is a strong choice; if you like a Laravel-style structure, Guren is. Void's deploy story is well done, and we will keep improving ours.

The methods, aggregate results and each run's code changes are in the evaluation report. Raw session logs, which hold full prompts, are not published.

To try Guren with the agent harness installed:

bunx create-guren-app my-app --agents claude

← All posts