- Home
- Skills
- Testing & Debugging
- Fixing Bugs — Strict TDD Red/Green Bug-Fix Cycle
Works with the AI Agents you already use
Fixing Bugs — Strict TDD Red/Green Bug-Fix Cycle
Fixes bugs test-first: reproduce with a failing test, apply the minimum fix, prove it green, then run the full suite.
Free
Fixing Bugs — Strict TDD Red/Green Bug-Fix Cycle
Example session with this skill installed
Fix BUG-417 from Sentry. A customer applied coupon WELCOME10 ($10 off) to a $5.00 cart and checkout returned a 500.
RangeError: amount must be a non-negative integer (got -500)
at buildPaymentIntent (src/checkout.js:6:11)
at handleCheckout (src/routes/checkout.js:14:20)
Expected: the coupon covers the whole cart and the total is $0.00. Product rule: a fixed coupon never takes a total below zero. There are no tests for pricing yet.
// src/pricing.js
export function totalCents(items, coupon) {
const subtotal = items.reduce((sum, i) => sum + i.unitCents * i.qty, 0);
if (!coupon) return subtotal;
if (coupon.kind === "percent") {
return Math.round((subtotal * (100 - coupon.value)) / 100);
}
return subtotal - coupon.value; // kind === "fixed", value in cents
}
// src/checkout.js
import { totalCents } from "./pricing.js";
export function buildPaymentIntent(cart) {
const amount = totalCents(cart.items, cart.coupon);
if (!Number.isInteger(amount) || amount < 0) {
throw new RangeError(`amount must be a non-negative integer (got ${amount})`);
}
return { amount, currency: cart.currency };
}
- Read your context and instructions
- Compiled the fixing bugs
Fixed. totalCents() let a fixed coupon push the total below zero. It now clamps at 0, and a regression test reproduces the exact Sentry error before the fix and passes after it.
I reproduced the two files from the report in a scratch project and ran everything below with Node's built-in test runner (node --test, Node 24). The output shown is real, with timing lines trimmed. Your project's full suite, linter and CI were
not run (see Next steps).
1. UNDERSTAND
GIVEN: a cart with one item at 500 cents and coupon WELCOME10 (kind "fixed", value 1000)
WHEN: checkout calls buildPaymentIntent(cart)
THEN: RangeError "amount must be a non-negative integer (got -500)" → HTTP 500
EXPECTED: payment intent with amount 0 (fixed coupons never go below zero)
Platform dimension: none. This is pure integer arithmetic with no filesystem, encoding, subprocess or time zone involved, so a single-OS run is enough.
2. Root cause analysis
ROOT CAUSE:
File: src/pricing.js
Function: totalCents()
Line: 7
Issue: the fixed-coupon branch returns subtotal - coupon.value with no lower bound;
500 - 1000 = -500, which buildPaymentIntent() correctly rejects
Fix: return Math.max(0, subtotal - coupon.value)
The guard in checkout.js is behaving correctly. The bug is in pricing.js.
3. Characterization tests (pricing.js had no tests)
These capture the current behavior first, so the fix can't silently change the other branches.
// test/pricing.characterization.test.js
import { test } from "node:test";
import assert from "node:assert/strict";
import { totalCents } from "../src/pricing.js";
const items = [{ unitCents: 2500, qty: 2 }]; // $50.00
test("characterize: no coupon returns the subtotal", () => {
assert.equal(totalCents(items, null), 5000);
});
test("characterize: percent coupon rounds to whole cents", () => {
assert.equal(totalCents([{ unitCents: 999, qty: 1 }], { kind: "percent", value: 15 }), 849);
});
test("characterize: fixed coupon smaller than subtotal is subtracted", () => {
assert.equal(totalCents(items, { kind: "fixed", value: 1000 }), 4000);
});
Result on the unmodified code: tests 3, pass 3, fail 0.
4. Failing test (RED)
The test goes through buildPaymentIntent(), the function in the Sentry stack trace, so it exercises the exact error path and not just the helper.
// test/checkout.bug-417.test.js
import { test } from "node:test";
import assert from "node:assert/strict";
import { buildPaymentIntent } from "../src/checkout.js";
test("should_build_zero_amount_intent_when_fixed_coupon_exceeds_subtotal", () => {
// Arrange: the exact cart from BUG-417, a $5.00 cart with WELCOME10 ($10 off)
const cart = {
items: [{ unitCents: 500, qty: 1 }],
coupon: { code: "WELCOME10", kind: "fixed", value: 1000 },
currency: "usd",
};
// Act
const intent = buildPaymentIntent(cart);
// Assert: the expected behavior, not the buggy one
assert.deepEqual(intent, { amount: 0, currency: "usd" });
});
TEST: should_build_zero_amount_intent_when_fixed_coupon_exceeds_subtotal
STATUS: FAILED
ERROR: RangeError: amount must be a non-negative integer (got -500)
at buildPaymentIntent (src/checkout.js:6:11)
MATCHES BUG REPORT: Yes. Same exception, message and frame as the Sentry trace.
5. Fix (minimum change)
--- a/src/pricing.js
+++ b/src/pricing.js
@@ -4,5 +4,5 @@ export function totalCents(items, coupon) {
if (coupon.kind === "percent") {
return Math.round((subtotal * (100 - coupon.value)) / 100);
}
- return subtotal - coupon.value; // kind === "fixed", value in cents
+ return Math.max(0, subtotal - coupon.value); // kind === "fixed", value in cents; never below zero
}
6. Passing test (GREEN)
TEST: should_build_zero_amount_intent_when_fixed_coupon_exceeds_subtotal
STATUS: PASSED
BEFORE FIX: RangeError: amount must be a non-negative integer (got -500)
AFTER FIX: pass
ROOT CAUSE: fixed-coupon branch had no lower bound
7. Regression check
$ node --test
✔ should_build_zero_amount_intent_when_fixed_coupon_exceeds_subtotal
✔ characterize: no coupon returns the subtotal
✔ characterize: percent coupon rounds to whole cents
✔ characterize: fixed coupon smaller than subtotal is subtracted
ℹ tests 4
ℹ pass 4
ℹ fail 0
- New bug-fix test passes (GREEN)
- Characterization tests still pass
- Your full unit and integration suite: not run. I only had these two files.
- Linter, formatter and type checker: not run
- CI: not run. The bug is not platform-related, so the normal pipeline is enough.
Brief explanation
totalCents()subtracted a fixed coupon without a lower bound, so coupons larger than the cart produced negative totals.- I clamped the fixed-coupon branch at 0. The percent branch and the checkout guard are unchanged.
- The bug-fix test stays in the suite as a permanent guard against this regression.
Suggested commit
fix: clamp fixed-coupon totals at zero
Bug: BUG-417
Root Cause: fixed-coupon branch in totalCents() had no lower bound (500 - 1000 = -500)
Fix: return Math.max(0, subtotal - coupon.value)
Test: should_build_zero_amount_intent_when_fixed_coupon_exceeds_subtotal
Next steps
- Run
npm test, the linter and the type checker in the real repo.
Open question, not fixed here: checkout now produces amount: 0. Many payment providers reject zero-amount charges, so confirm whether $0 orders should skip the payment step. That is a product decision and a separate change.
3. Possible follow-up ticket: a percent coupon above 100 would also go negative. The report didn't cover that case, so I left it alone.
Connects securely to your tools. The creator never sees your data.
About this skill
Fixing Bugs makes your agent prove a bug exists before it touches the code. A fix only counts if a test goes from RED (failing for the reason in the bug report) to GREEN, and the rest of the suite stays green.
The problems it solves
- "Fixes" that change five things and hope one of them works
- Tests that cover an error path similar to the reported one instead of the exact one (e.g.
CancelledErrorwhen Sentry reportedGeneratorExit) - Bug fixes that quietly grow into refactors
- Platform bugs reproduced on Linux when they were reported on Windows
The workflow (strict order): UNDERSTAND (GIVEN/WHEN/THEN/EXPECTED, platform dimension, investigation technique) → REPRODUCE (failing test named should_<expected>_when_<condition>) → VERIFY RED → FIX (root cause written down first, minimum change, scope rules) → VERIFY GREEN → VALIDATE (full suite, CI matrix if the bug is platform-related) → optional REFACTOR in a separate commit. The output is a fixed sequence: root cause, characterization tests, failing test, fix, passing test, regression check, short explanation.
What's inside
SKILL.md: the cycle, scope rules, how to handle pre-existing issues, logging rules for fixes, output formatreferences/cycle.md: phase-by-phase detail, the exact-error-path rule, test naming, AAA structurereferences/investigation.md: 5 Whys, automatedgit bisect, a debugging journal, bug clustering, anti-patternsreferences/checklist.md: the end-of-fix checklist, commit format, edge cases (several bugs in one report, untested code, bugs that need an integration test, regressions)learnings/: project-specific bug patterns that override the defaults
When it triggers: a bug report, "fix this", a Sentry alert, a regression, or a pasted stack trace.
Free and MIT-licensed, part of the claude-skills-czl collection.
Source on GitHub: this skill · full repository
How to use
Skills on Agensi run inside your AI agent. Copy the prompt, paste it into your agent, and it connects to Agensi and installs this skill for you. If a payment is needed, your agent shows it to you first.
Reviews
No reviews yet
Be one of the first to try it. Every listed skill passes our trust checks below.
Security scanned
Passed our 8-point scan before listing
Fresh listing
Recently published to Agensi
Free forever
No account required to browse
Trust & safety
Security scanned
Verified clean 11 days ago
- Free to download with an account