Works with the AI Agents you already use

    Claude CodeClaude CodeCursorCursorCodex CLICodex CLIMuseMuseDotsDots+22 more

    Fixing Bugs — Strict TDD Red/Green Bug-Fix Cycle

    1

    Fixes bugs test-first: reproduce with a failing test, apply the minimum fix, prove it green, then run the full suite.

    Free

    Security scanned
    Fixing Bugs — Strict TDD Red/Green Bug-Fix Cycle

    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

    1. 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. CancelledError when Sentry reported GeneratorExit)
    • 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 format
    • references/cycle.md: phase-by-phase detail, the exact-error-path rule, test naming, AAA structure
    • references/investigation.md: 5 Whys, automated git bisect, a debugging journal, bug clustering, anti-patterns
    • references/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

    Listed11 days ago

    What's inside

    Frequently Asked Questions