Back to Insights

Han Can Run Your Tests. Can They Catch the Bug?

A swallowed database error shows the limits of automated approval

5 min readBy The Bushido Collective
Claude CodeDeveloper ToolsEngineering ExcellenceAIOpen Source
Share:LinkedInX
Suppose an AI-written helper catches a database error and returns an empty array. Its test checks only that the function returns a value. You now have a passing test for the behavior that hides the failure.

In Jest, toBeDefined() checks that a value isn’t undefined. An empty array qualifies. If the importer treats that array as a successful read with no records, it can’t distinguish an unavailable database from an empty one.

The code and the test agree. Another assistant might spot the problem. So might a human. But if either accepts the same assumption about empty results, approval adds no evidence that the helper handles a failed read. Changing the author doesn’t change what the test observes.

We built Han, our marketplace of Claude Code plugins, to put executable checks into the assistant’s workflow. The project is MIT licensed, with plugins for testing tools, language checks, and specialist agents. Its test hooks address the case where a useful test never gets run. The test above needs work before a hook can help.

The test has to tell the difference

Assume the importer’s contract requires failed reads to reach the caller as errors. A useful test supplies a failing data source and checks that the helper reports the failure. A separate test supplies a successful empty response and expects an empty list. Those inputs look identical after the broken helper has finished with them, but they require different outcomes.

For an interface that reports errors through rejected promises, a Jest assertion using .rejects fails if the promise fulfills. Await or return the assertion so Jest waits for the outcome. Expecting rejection when the data source fails would catch the helper returning an empty array instead. An interface that returns an explicit error result needs a test for that result. Throwing an exception is one design choice; preserving the distinction is the requirement in this example.

The empty-success case matters just as much. A helper that throws on every read could satisfy the rejection test while breaking valid imports. Requiring the successful empty read to stay successful rules out that repair. If these tests supply a fake data source, they establish how the helper handles those responses; they leave the real database connection untested.

A reversed condition needs the same attention to expected behavior. If the requirement is to import active records, supply an active record and an inactive one, then check exactly which record reaches the destination. A test that merely checks that the import returned something cannot distinguish the correct condition from its inverse.

This is where the reviewer earns the approval. The expected result has to come from the requirement, rather than being copied from whatever the implementation happens to return. Otherwise the test can turn the mistake into the rule.

What Han actually runs

Han’s jutsu-jest plugin offers a concrete example. In the linked source snapshot, its default configuration selects directories with a jest entry in package.json and runs npm test there. The repository’s test script and assertions determine what gets checked.

The plugin registers a command on Claude Code’s Stop event. Stop runs when the assistant considers finishing its work, as Anthropic’s hook reference explains. The hook gives the test command a place in that workflow instead of leaving execution to the assistant’s memory.

In that version, the runner reports a blocking error when a command fails. With the error-handling test in place, a failing run can return a failed-check report to Claude for correction. With the existence-only assertion, the same hook can return success. It has faithfully run an inadequate test.

There are execution limits too. The Jest hook uses caching, so matching files that haven’t changed can skip a run. The runner also exits successfully when it finds no target directories. Look at which command ran, in which package, before treating a clean exit as evidence about the code.

Try it against the broken version

To evaluate Han in a repository, use a defect the repository’s tests should catch. On a disposable branch, put the swallowed error back into the helper, leaving the test inputs and expectations unchanged. Run the test directly. Its failure needs to identify the wrong outcome, rather than a syntax error or a missing dependency.

Then let Claude reach the end of its turn and inspect whether the hook reports that test failure from the same package. If the direct run fails but the hook returns success, inspect target selection and caching before changing the assertions. After correcting the helper, require both the failed-read and empty-success cases to pass with their original expectations.

That exercise separates a missing assertion from a command that never ran. It also puts something outside the model’s own appraisal into the review: an observable difference between the broken implementation and the corrected one.

Keep required checks in CI, the shared build that runs on pull requests. A local Stop hook gives the assistant feedback while it works; it doesn’t establish the repository’s merge policy. If your existing scripts already provide that feedback reliably, you can keep them. Han offers a way to package those checks for Claude Code rather than wire each one yourself.

Start with the helper, not the plugin count. If returning an empty array after a failed read still earns a pass, the next change belongs in the test.

Want this looked at in your business?

Start with the rough map: thirty minutes, owner to owner, and a written report on where AI pays off for you and what it's worth. It's free, and if you don't need us the report says so.

Get your rough map, free

Not ready to talk? Stay sharp anyway.

We send insights like this to technical leaders every week or two. The thinking we bring to our engagements, no fluff, no spam.

Keep reading

Share:LinkedInX