I used HyperProbe to debug a checkout bug without adding another log statement.
The API Returned 200 OK. The Result Was Still Wrong.
Most debugging examples start with something obvious.
The server crashes.
An exception appears.
The API returns 500.
This one didn’t.
I built a small checkout scenario where a premium customer was supposed to receive a 20% discount on a ₹1,000 order.
The endpoint returned successfully:
{
"customerId": "legacy_42",
"subtotal": 1000,
"finalPrice": 998,
"currency": "INR"
}
200 OK.
No exception.
No failed request.
But the customer should have been charged ₹800, not ₹998.
I used this intentionally small bug to try HyperProbe and answer a question I care about when debugging production systems:
What if the application is technically working, but the state inside it is wrong?
The bug
The pricing function was simple:
export function calculateFinalPrice(
customer: Customer,
subtotal: number
) {
const finalPrice =
subtotal * (1 - customer.discount / 100);
return finalPrice;
}
For current customer records, discounts were stored like this:
{
id: "current_42",
plan: "premium",
discount: 20
}
So:
1000 × (1 - 20 / 100)
= 800
Everything works.
But an older customer record used a different representation:
{
id: "legacy_42",
plan: "premium",
discount: 0.2
}
Here, 0.2 represents 20%.
The pricing code assumes discounts are stored as percentage points, so it does:
1000 × (1 - 0.2 / 100)
= 998
The important part is that none of this creates an error.
The application happily returns the wrong number.
The usual debugging move
My first instinct in this kind of situation would normally be to add logs around the calculation:
console.log({
customer,
subtotal,
finalPrice,
});
Then reproduce the problem.
If the service were running somewhere remotely, that could also mean:
- changing the code,
- rebuilding,
- deploying,
- waiting for the affected path to execute again,
- finding the right logs,
- then removing the temporary logging later.
For a tiny demo, that sounds trivial.
In a production application with a rare bug, noisy logs, and requests you cannot reproduce whenever you want, it gets less trivial very quickly.
That was the part of HyperProbe I wanted to test.
Adding a runtime snapshot instead
I connected the Node application to HyperProbe and added a Snapshot probe around the pricing function.
I watched:
customer.id
customer.plan
customer.discount
subtotal
finalPrice
Then I triggered the same checkout again.
HyperProbe captured the values from the running process:
subtotal = 1000
finalPrice = 998
customer.id = "legacy_42"
customer.plan = "premium"
customer.discount = 0.2
It also showed the call stack pointing back to calculateFinalPrice.
At that point the bug stopped being mysterious.
The request was fine.
The endpoint was fine.
The calculation was doing exactly what the code told it to do.
The unexpected value was already present in the customer data.
That distinction matters.
Why I liked this more than another log
The useful part wasn’t that HyperProbe could display five variables.
I can do that with console.log.
The useful part was where the observation happened.
I could ask the running application:
What values did you actually have when this line executed?
without adding another debugging statement to the code path.
That becomes much more interesting for bugs such as:
- only one customer is affected,
- a bad value appears only under a specific request,
- the endpoint succeeds but downstream state is wrong,
- retries behave differently in production,
- an integration returns technically valid but unexpected data.
These are often harder than crashes because there is no obvious error boundary telling you where to start.
Conditional probes make this more useful
For a real service, I wouldn’t want to capture every checkout.
I could scope the probe to the affected customer:
customer.id === "legacy_42"
Now the debugging question becomes much more targeted:
Show me what happens here, but only when this specific customer reaches the code path.
That is closer to how I would want to investigate an issue in a busy production system.
Instead of increasing logging for everyone, inspect the execution you actually care about.
One thing that surprised me while setting it up
The most interesting issue I hit wasn’t in the checkout code.
It was source mapping.
The application was written in TypeScript, but HyperProbe instruments the compiled JavaScript running in Node.
So it needs to map something like:
src/checkout.ts:27
to the correct location inside:
dist/checkout.js
My first probe failed with:
Coordinate src/checkout.ts not indexed in commit ...
The Git commit was correct.
The source file existed in that commit.
The compiled JavaScript existed.
The source map existed too.
What eventually mattered was getting the updated source maps uploaded and indexed by the running HyperProbe agent.
Once the agent logged:
Found 3 source map files
Streaming source map: dist/checkout.js.map
Source map upload finished: true
Source map upload completed
The probe could be created successfully.
I actually liked seeing this constraint.
HyperProbe wasn’t blindly pretending that a line in my editor matched a line in the running process. It needed enough information to connect the source I was looking at to the code that was actually executing.
For a runtime debugging tool, that mapping has to be trustworthy.
Runtime evidence changes the debugging question
There are two different questions you can ask when something goes wrong:
What does the code look like it should do?
and:
What did the application actually see when it ran?
Usually we start with the first one.
We read the function.
We inspect types.
We form a hypothesis.
Then we add logs or recreate the environment to test it.
In this example, HyperProbe let me get to the second question earlier.
The snapshot showed:
discount = 0.2
and suddenly the rest of the behavior made sense.
The bug itself was intentionally simple.
But the workflow is what I wanted to understand.
Where I think this is most useful
I wouldn’t reach for a runtime probe for every bug.
If a unit test immediately reproduces the issue, I’d fix the test and move on.
If the application throws a clear exception with a useful stack trace, normal observability might already be enough.
Where this gets interesting is the class of bugs where:
- execution succeeds,
- logs don’t contain the state you need,
- reproducing locally is difficult,
- or only a narrow production condition triggers the issue.
Those are the cases where seeing live values at a precise point can shorten the distance between:
“Something is wrong.”
and:
“This is the exact value that made it wrong.”
That’s what I wanted to test with HyperProbe.
And for this small checkout example, it did exactly that.
Top comments (0)