It's Friday night. Users can't checkout. Payment processing crashes with a null pointer. S0 Critical.
โฑ ~15 min ยท Hands-on
PagerDuty fires at 21:47 on a Friday. The checkout page throws a 500 error. Revenue is dropping. Users cannot complete purchases. The error log reads:
ERROR 2026-04-04T21:47:12Z PaymentService.calculateTotal()
NullPointerException: Cannot read property 'amount' of null
at OrderProcessor.applyDiscount(OrderProcessor.java:142)
at PaymentService.calculateTotal(PaymentService.java:87)
at CheckoutController.processOrder(CheckoutController.java:53)
Before writing any code, classify the incident. This determines which workflow to follow.
| Severity | Impact | Response |
|---|---|---|
| S0 Critical | Revenue-impacting, users blocked | Hotfix flow (this tutorial) |
| S1 High | Major feature broken, workaround exists | Expedited bugfix sprint |
| S2 Medium | Minor feature degraded | Normal sprint backlog |
| S3 Low | Cosmetic / edge case | Backlog grooming |
Checkout is completely down. Users cannot pay. This is S0 Critical โ we use the hotfix flow, not a regular bugfix.
Hotfix branches always branch from main, never from develop. Main represents what's in production. When an S0 incident is active, /agile-code-branch detects the hotfix context and automatically branches from main.
/agile-code-branch
๐ฟ Branch created: hotfix/FIX-null-payment
Source: main
Convention: hotfix/FIX-[short-description]
Incident: S0 โ Payment processing null pointer
Switched to branch 'hotfix/FIX-null-payment'
This creates an isolated branch directly from the production codebase. No unfinished feature work leaks in.
Instead of manually running git checkout -b, /agile-code-branch automatically applies your project's branch naming convention, ensures hotfix branches source from main (not develop), and links the branch to the incident. No typos, no risk of branching from the wrong base.
Run /agile-code-tdd to start a test-driven fix. First, write a failing test that reproduces the crash.
/agile-code-tdd
@Test
void should_not_crash_when_discount_is_null() {
Order order = new Order();
order.setSubtotal(99.99);
order.setDiscount(null); // No discount applied
double total = paymentService.calculateTotal(order);
assertEquals(99.99, total, 0.01);
}
Run it:
$ ./gradlew test --tests "PaymentServiceTest.should_not_crash_when_discount_is_null"
PaymentServiceTest > should_not_crash_when_discount_is_null FAILED
java.lang.NullPointerException: Cannot read property 'amount' of null
at OrderProcessor.applyDiscount(OrderProcessor.java:142)
1 test failed
Confirmed โ the test reproduces the exact production crash. Now find the root cause:
// OrderProcessor.java:142 โ THE BUG
public double applyDiscount(Order order) {
return order.getSubtotal() - order.getDiscount().getAmount(); // ๐ฅ No null check!
}
// OrderProcessor.java:142 โ FIXED
public double applyDiscount(Order order) {
if (order.getDiscount() == null) {
return order.getSubtotal();
}
return order.getSubtotal() - order.getDiscount().getAmount();
}
Run the test again:
$ ./gradlew test --tests "PaymentServiceTest.should_not_crash_when_discount_is_null"
PaymentServiceTest > should_not_crash_when_discount_is_null PASSED
1 test passed
GREEN. The fix works. The null discount no longer crashes the checkout.
A hotfix must pass the full pipeline โ no shortcuts.
/agile-code-ci
CI Pipeline โ hotfix/FIX-null-payment
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Build ................ โ
PASS (12s)
Unit Tests ........... โ
247/247 passing (34s)
Integration Tests .... โ
18/18 passing (22s)
Lint ................. โ
0 issues
Security Scan ........ โ
0 vulnerabilities
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Result: โ
ALL CHECKS PASSED
Even under pressure, every hotfix gets reviewed. Use /agile-code-pr-review with expedited flag.
/agile-code-pr-review
PR Review โ hotfix/FIX-null-payment
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Reviewer: @lead (expedited โ 30 min max)
Changed: 1 file, +4 lines, -1 line
Findings:
(none โ minimal, focused fix)
Verdict: โ
APPROVED
Comment: "Clean fix. Null guard is correct.
Consider Optional wrapper as follow-up."
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Time to approval: 14 minutes
An expedited review has a 30-minute SLA, but the same quality standards apply. The reviewer checks correctness, test coverage, and no regressions. The only thing that's faster is the turnaround time.
A hotfix must land in both branches. Main for production. Develop so the fix isn't lost in the next release. /agile-code-merge detects the hotfix branch and handles both merges plus version tagging in one command.
/agile-code-merge
๐ Merge โ hotfix/FIX-null-payment
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Pre-merge checks:
โ
PR approved by @lead (expedited)
โ
CI passing on branch
โ
No merge conflicts (main)
โ
No merge conflicts (develop)
Merging: hotfix/FIX-null-payment โ main
Strategy: merge --no-ff (hotfix convention)
โ
Merged to main
Merging: hotfix/FIX-null-payment โ develop
Strategy: merge --no-ff (hotfix convention)
โ
Merged to develop
Tagging: v1.2.1 (patch increment from v1.2.0)
โ
Tag v1.2.1 created and pushed
Branch hotfix/FIX-null-payment deleted (was 9c4e1b2).
โ
Hotfix merged to main + develop, tagged v1.2.1.
/agile-code-merge detects the hotfix/ branch prefix and automatically performs both merges (to main and develop), creates the patch version tag, and cleans up the branch. This eliminates the risk of forgetting the develop merge or mistagging the release.
If the fix only lands in main, the next release from develop will reintroduce the bug. /agile-code-merge enforces both merges as mandatory for hotfix branches โ it will not complete until both succeed.
/agile-ship-deploy
Deploy โ v1.2.1 โ production
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Rolling deploy ........ started
pod-1/3 ............. โ
healthy
pod-2/3 ............. โ
healthy
pod-3/3 ............. โ
healthy
Health checks ......... โ
all endpoints responding
Smoke test: checkout .. โ
order placed successfully
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Deploy complete: 22:31 UTC
Incident duration: 44 minutes
If the hotfix deploy causes errors โ health checks fail, error rates spike, or smoke tests don't pass โ roll back immediately:
/agile-ship-rollback
โช Rollback โ v1.2.1 โ v1.2.0
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Reason: deploy health check failure
Rolling back to: v1.2.0 (last known good)
pod-1/3 ............. โ
rolled back
pod-2/3 ............. โ
rolled back
pod-3/3 ............. โ
rolled back
Health checks ......... โ
all endpoints responding
Error rate ............ โ
returned to baseline
Rollback complete. Production is on v1.2.0.
โ ๏ธ Investigate the failed hotfix before re-deploying.
/agile-ship-rollback reverts production to the last known good version. It's always safer to roll back and investigate than to push a second fix under pressure.
OrderProcessor.applyDiscount() assumed discount is always present. Orders without discount codes triggered a NullPointerException.A hotfix follows the same quality standards as any other change: TDD, code review, full CI. The difference is speed of turnaround and priority of scheduling โ not a reduction in rigor. Cutting corners under pressure is how you create the next S0 incident.
/agile-code-branch to create a hotfix branch from main (not develop), ensuring the fix is based on production code/agile-code-tdd to reproduce the crash, then fix with confidence/agile-code-ci to verify no regressions in the full suite/agile-code-pr-review with expedited review (30 min SLA)/agile-code-merge to merge to both main and develop, tag the patch release โ all enforced automatically for hotfix branches/agile-ship-deploy to push to production and run smoke tests/agile-ship-rollback as the safety net if the deploy fails or causes new issuesWhere does a hotfix branch from?