# Build vs. Buy Internal Tools: We Tested an AI-Built App

A working app, a real alternative, and the tests beyond the demo. What our equipment-lending experiment revealed about building internal tools with AI.

Author: Cerrito
Published: 2026-09-22
Updated: 2026-09-22
Canonical: https://cerrito.ai/blog/build-vs-buy-ai-internal-tools/

![Build vs. Buy: a ready-made ivory bridge beside a modular orange bridge being tested by a tiny figure. We tested an AI-built app.](https://cerrito.ai/research/internal-tool-v2/build-vs-buy-cover.png)

Our AI-built app said “success.” The new laptop request hadn’t been created.

By then, it had already handled approvals, revoked a departed borrower’s access, and stopped two people checking out the same equipment.

A submission for a different laptop, sent with the earlier submission’s identifier, exposed the mistake.

That was one reason to keep testing. The other was a question the code couldn't answer: **was this tool worth owning at all?**

We built a working equipment-lending app for a fictional 50-person company. We changed its approval rules, revoked a borrower's access, raced two checkouts, and restored its data. Then we tried the basic lending workflow in an actual alternative: Snipe-IT.

Here is what worked, what needed a repair, and what changed the build-versus-buy decision.

[![The working Equipment Desk app, signed in as Casey, with equipment checked out and an activity history.](https://cerrito.ai/research/internal-tool-v2/custom-checkout.webp)](https://cerrito.ai/research/internal-tool-v2/custom-checkout.webp)

Our local app after a real browser checkout. All people and equipment are synthetic. This is a running application, not a design mockup.

**Scope:** one AI assistant built and tested one local Python/SQLite app against known scenarios. No real employees participated. Snipe-IT's public demo received a smaller set of checks; this is a worked decision, not a feature-parity benchmark. Source, the first failure, repaired results, and methodology are available below.

## First, give the app a job worth finishing

“Build me an equipment tracker” is easy to satisfy with a table and a few buttons.

We required a complete loop: an employee requests equipment, a manager approves it, the employee borrows it, and someone records its return. The app had server-enforced roles, password sessions, persistent records, and a history of actions.

It started with 50 synthetic accounts, four departments, and 20 assets. Those are fixture sizes, not a claim that we tested 50 people using it simultaneously.

The first complete lending loop passed.

Useful—but it still left the most interesting question open. What happens when the company changes while there is work in progress?

## Change the rules with a request still open

At first, any manager could approve equipment requests.

We then introduced a new rule: a Design employee needs a Design manager's approval. Older requests should keep the policy under which they were submitted.

That last sentence is the feature.

![Existing requests retain approval by any manager. New requests require a manager from the same department.](https://cerrito.ai/research/internal-tool-v2/policy.svg)

The policy choice we implemented and tested: preserve existing work, apply the new rule to new requests.

Without it, a “simple” approval change could quietly rewrite an existing commitment. We chose to preserve old requests; another company might reasonably choose to reapprove them. The point is to make the choice explicit.

The app stored a policy version on each request. After the change:

* An Engineering manager could still approve the older Design request.
* The same manager was refused on a new Design request.
* The Design manager could approve the new one.
* Existing requests and events were unchanged immediately after the migration.

[![The app refuses an Engineering manager's approval with the message: This request needs a manager from Design.](https://cerrito.ai/research/internal-tool-v2/custom-wrong-approver.webp)](https://cerrito.ai/research/internal-tool-v2/custom-wrong-approver.webp)

The new rule enforced by the server: the wrong manager receives a refusal. Older work retains its original policy.

This was the strongest case for the custom build: we could encode a precise decision about how *our* process should evolve.

It does not prove Snipe-IT cannot support that requirement. We did not establish its configuration or extension options for this policy. Before commissioning a build, that is exactly what we would investigate.

## Take away the borrower's access

Next, Alex borrowed a laptop. The administrator deactivated Alex's account while the loan remained open.

Could Alex's already-open session still write to the app? No. The server rejected the return attempt.

Could another ordinary employee mark it received? No.

Could the administrator receive it with a note? Yes. The history attributed the receipt to the administrator, rather than pretending Alex had returned it through their account.

This distinction matters when a colleague leaves. Removing access should not remove the company's ability to finish the work—or its ability to tell who actually finished it.

The app passed this check too.

## Two checkouts worked. One retry didn't.

We approved two requests for the same equipment and sent their checkout operations concurrently.

One succeeded. One received a conflict. The database contained one active checkout and one checkout event for those competing requests.

We also repeated an identical checkout command. It returned the original result without adding a second event. That is what a retry should do.

Then came the failed check from the opening.

We submitted a request for Laptop 05. We reused its command key—the identifier that tells the server a submission is a retry—but changed the equipment to Laptop 06.

The first implementation returned the earlier request as a successful duplicate.

**Laptop 06 had never been requested. The response still looked successful.**

![A request for Laptop 06 reused Laptop 05’s key and incorrectly returned the old result. After repair, the changed request was refused with HTTP 409.](https://cerrito.ai/research/internal-tool-v2/retry.svg)

A visual summary of the retained API results, before and after the repair.

The repair made the server check the original equipment and note before replaying a result. Reusing the key for a different operation then returned a conflict.

This was an API-level test; we did not observe an employee causing it through the interface. It is a defect in this implementation, not evidence that AI-built software generally fails at retries.

The useful lesson is narrower: testing “click twice” would have missed the difference between **the same request twice** and **a different request with the same identifier**.

The first run stopped at that failure. After the repair, all ten defined scenarios passed, including starting another server from a backup and importing an export into a fresh database. The restored business records matched. Imported accounts received reset synthetic passwords; active sessions were intentionally excluded.

## Now try the software you could adopt instead

A custom app is easy to justify against an imaginary expensive suite.

We used an actual alternative. In Snipe-IT's public demo, we created a clearly labeled synthetic asset, checked it out to its demo Test User, and checked it back in as an administrator.

The checkout and check-in succeeded. Opening the checkout page again while the asset was assigned produced an unavailable message. After check-in, the asset was ready to deploy, with one checkout, one check-in, and a history count of three.

[![Snipe-IT public demo showing our synthetic asset ready to deploy after administrator check-in.](https://cerrito.ai/research/internal-tool-v2/vendor-checked-in.webp)](https://cerrito.ai/research/internal-tool-v2/vendor-checked-in.webp)

Snipe-IT's actual public demo after our test. Product interface: Snipe-IT / Grokability. The synthetic asset was created for this research.

We used authenticated HTTP forms for these operations and inspected the resulting browser pages. Some browser controls were unreliable in our automation session, so we make no comparison of interaction speed or ease of use.

Nor did we run equivalent offboarding and recovery checks on Snipe-IT. The public demo resets repeatedly and disables changes to access levels, passwords, and email. Those restrictions are properties of the demo, not missing product capabilities. [Demo scope and restrictions](https://snipeitapp.com/demo)

| Check                    | Our AI-built app                                  | Snipe-IT public demo                               |
| ------------------------ | ------------------------------------------------- | -------------------------------------------------- |
| Lending workflow         | Request, approve, checkout, return passed         | Admin checkout and check-in passed                 |
| Assigned equipment       | Two competing checkout writes: one accepted       | Checkout page refused while assigned; no race test |
| Approval policy change   | Old policy retained; new department rule enforced | Not tested                                         |
| Offboarding              | Existing session revoked; admin receipt passed    | Not tested                                         |
| Changed retry payload    | Failed initially; passed after repair             | Not tested                                         |
| Recovery and portability | Local backup restore and fresh import matched     | Not tested                                         |

This table is a record of observations, not a score. More checks on the app we controlled do not make it the more capable product.

## The price changed the decision

Snipe-IT listed Basic Hosting at **$39.99 per month, or $399.99 billed annually**, with unlimited users and assets. Its self-hosted software was free. Hosted service includes server maintenance and upgrades. These were published prices checked on September 22, 2026; we did not purchase a plan. [Official pricing](https://snipeitapp.com/pricing)

That is a very different comparison from “50 employees times an expensive seat price.”

For the standard lending job we exercised, the argument for rebuilding simply to eliminate a subscription became much weaker.

Here is a sensitivity check, not a measured cost result: at an assumed $100 per hour, the $399.99 annual fee equals roughly **four hours of work a year**. At $50 per hour, it equals roughly eight.

Those are the hours of *additional* ownership work that would consume the subscription saving, before including the custom app's hosting, initial build, or AI costs. Both routes still need someone to administer equipment and users. Custom workflow benefits might justify much more investment.

We did not measure those costs or benefits. The experiment therefore cannot tell you which route is cheaper in total.

It can tell you where to look: **price the smallest credible alternative before estimating what you will save by replacing it.**

## When should you build an internal tool with AI?

For a standard equipment checkout process, we would evaluate existing software first. It completed the basic job in this trial, and the published hosted price creates a low bar for the value of saved engineering attention.

We would consider a custom build when a valuable requirement survives that evaluation: a policy, integration, or workflow that existing software cannot accommodate acceptably. Name that requirement and measure the workaround it replaces. “We prefer our own UI” can be a reason; it still needs a value attached.

Then require more than a successful demo:

1. **Change a rule with work in progress.** Decide what happens to old requests and approvals.
2. **Remove someone's access mid-workflow.** Prove that their session stops working and an authorized person can finish the job.
3. **Repeat and collide operations.** Test exact retries, changed requests, and competing writes.
4. **Recover somewhere fresh.** Start the application from a backup; do not stop at creating a backup file.
5. **Leave with the records.** Import the export and check relationships, history, and account behavior.

Finally, assign an owner for incidents, updates, and the next policy change. This trial did not test months of maintenance, production security, SSO, or actual employee adoption.

## Our verdict: start with Snipe-IT

THE DECISION · THIS EXPERIMENT

Start with Snipe-IT.

Validate the approval requirement before committing.

WHY START HERE

Basic lending worked.\
A custom replacement’s value remains unproven.

WHAT CHANGES OUR MIND

An essential approval policy that existing software cannot accommodate acceptably.

That policy fit is still untested in Snipe-IT.

**For this fictional company, we would start with Snipe-IT and validate the approval requirement before committing.** Our experiment did not establish a requirement valuable enough to justify a custom replacement.

The existing tool completed the basic lending job, and its published hosting price made subscription savings alone a weak reason to rebuild. Our app passed ten defined checks after a repair, but we had not established its production security, long-term maintenance burden, or adoption by employees.

The strongest reason to build was control over the approval policy—including what happens to requests already in progress. We would reconsider our verdict if that policy proved essential and Snipe-IT could not accommodate it acceptably. We have not tested that fit yet.

**The custom app proved it could handle our scenarios. It had not yet proved it was the better choice to own.**

## Inspect or repeat the experiment

The [research bundle](https://cerrito.ai/research/internal-tool-v2/experiment.zip) contains the working app, setup instructions, test harness, protocol, original source before repair, and both result files. The [methodology](https://cerrito.ai/research/internal-tool-v2/methodology.txt) is also readable separately, as are the [ten final results](https://cerrito.ai/research/internal-tool-v2/results.json).

The same AI assistant designed, implemented, exercised, and wrote up this case. Scenarios were known during implementation; there was no blind evaluation or independent human handover. The retained source and receipts let you inspect these particular claims. They do not turn one local experiment into a general benchmark for AI coding.
