FAILURE MAP
← Case archive

FA-30836 / HTTP retries / Open access

Retry hook failure containment: Cancel pending hook violates retry transition semantics · case 01

Cancel pending hook violates retry transition semantics

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

Cancel pending hook violates retry transition semantics

VERIFIED REPAIR

Restore the specified transition elif e[0]=='cancel': state='cancelled'.

Unsuccessful approach: The attempted repair changes the faulty site to elif e[0]=='cancel': state='running' but still violates a regression oracle.

Case contract

begin(cap) starts pending request. fail(eligible,before-hook-ok) calls retry hook only for eligible failures with remaining cap; a failing hook terminally records hook-error without spending retry budget. Otherwise admit consumes one cap and enters waiting. wake starts it. success(observer-ok) stores success before observer invocation; observer failure is diagnostic only and never creates a retry. cancel is terminal. Return lifecycle decisions and remaining cap.

Why this case matters

Offline deterministic model of HTTP request retries.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(events):
    cap=0; state='idle'; out=[]
    for e in events:
        if e[0]=='begin': cap=e[1]; state='running'
        elif e[0]=='fail':
            if state!='running': out.append('ignored'); continue
            if not e[1]: state='failed'; out.append('fatal'); continue
            if cap==0: state='failed'; out.append('exhausted'); continue
            if not e[2]: state='failed'; out.append('hook-error'); continue
            cap-=1; state='waiting'; out.append('scheduled')
        elif e[0]=='wake':
            if state=='waiting': state='running'; out.append('started')
        elif e[0]=='success':
            if state!='running': out.append('ignored'); continue
            state='done'; out.append('success')
            if not e[1]: out.append('observer-error')
        elif e[0]=='cancel': state='waiting'
    return [out,cap,state]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('0', solve([]), [[],0,'idle'])
check('1', solve([('begin',2),('fail',True,True),('fail',True,True),('wake',),('success',False),('fail',True,True)]), [['scheduled','ignored','started','success','observer-error','ignored'],1,'done'])
check('2', solve([('begin',N),('fail',True,False),('wake',)]), [['hook-error'],N,'failed'])
check('3', solve([('begin',N),('fail',False,False)]), [['fatal'],N,'failed'])
check('4', solve([('begin',0),('fail',True,False)]), [['exhausted'],0,'failed'])
check('5', solve([('begin',N),('success',True),('success',False)]), [['success','ignored'],N,'done'])
check('6', solve([('begin',N),('fail',True,True),('cancel',),('wake',),('success',False)]), [['scheduled','ignored'],N-1,'cancelled'])
check('7', solve([('begin',N),('fail',False,True)]), [['fatal'],N,'failed'])
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
0[[], 0, 'idle'][[], 0, 'idle']Passed
1[['scheduled', 'ignored', 'started', 'success', 'observer-error', 'ignored'], 1, 'done'][['scheduled', 'ignored', 'started', 'success', 'observer-error', 'ignored'], 1, 'done']Passed
2[['hook-error'], 1, 'failed'][['hook-error'], 1, 'failed']Passed
3[['fatal'], 1, 'failed'][['fatal'], 1, 'failed']Passed
4[['exhausted'], 0, 'failed'][['exhausted'], 0, 'failed']Passed
5[['success', 'ignored'], 1, 'done'][['success', 'ignored'], 1, 'done']Passed
6[['scheduled', 'started', 'success', 'observer-error'], 0, 'done'][['scheduled', 'ignored'], 0, 'cancelled']Failed
7[['fatal'], 1, 'failed'][['fatal'], 1, 'failed']Passed

SHA-256 / 41e8f96584d4a18ba4d7b41b010f325f5b2098aec9a3a6917e093d680276dcf8

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(events):
    cap=0; state='idle'; out=[]
    for e in events:
        if e[0]=='begin': cap=e[1]; state='running'
        elif e[0]=='fail':
            if state!='running': out.append('ignored'); continue
            if not e[1]: state='failed'; out.append('fatal'); continue
            if cap==0: state='failed'; out.append('exhausted'); continue
            if not e[2]: state='failed'; out.append('hook-error'); continue
            cap-=1; state='waiting'; out.append('scheduled')
        elif e[0]=='wake':
            if state=='waiting': state='running'; out.append('started')
        elif e[0]=='success':
            if state!='running': out.append('ignored'); continue
            state='done'; out.append('success')
            if not e[1]: out.append('observer-error')
        elif e[0]=='cancel': state='running'
    return [out,cap,state]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('0', solve([]), [[],0,'idle'])
check('1', solve([('begin',2),('fail',True,True),('fail',True,True),('wake',),('success',False),('fail',True,True)]), [['scheduled','ignored','started','success','observer-error','ignored'],1,'done'])
check('2', solve([('begin',N),('fail',True,False),('wake',)]), [['hook-error'],N,'failed'])
check('3', solve([('begin',N),('fail',False,False)]), [['fatal'],N,'failed'])
check('4', solve([('begin',0),('fail',True,False)]), [['exhausted'],0,'failed'])
check('5', solve([('begin',N),('success',True),('success',False)]), [['success','ignored'],N,'done'])
check('6', solve([('begin',N),('fail',True,True),('cancel',),('wake',),('success',False)]), [['scheduled','ignored'],N-1,'cancelled'])
check('7', solve([('begin',N),('fail',False,True)]), [['fatal'],N,'failed'])
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
0[[], 0, 'idle'][[], 0, 'idle']Passed
1[['scheduled', 'ignored', 'started', 'success', 'observer-error', 'ignored'], 1, 'done'][['scheduled', 'ignored', 'started', 'success', 'observer-error', 'ignored'], 1, 'done']Passed
2[['hook-error'], 1, 'failed'][['hook-error'], 1, 'failed']Passed
3[['fatal'], 1, 'failed'][['fatal'], 1, 'failed']Passed
4[['exhausted'], 0, 'failed'][['exhausted'], 0, 'failed']Passed
5[['success', 'ignored'], 1, 'done'][['success', 'ignored'], 1, 'done']Passed
6[['scheduled', 'success', 'observer-error'], 0, 'done'][['scheduled', 'ignored'], 0, 'cancelled']Failed
7[['fatal'], 1, 'failed'][['fatal'], 1, 'failed']Passed

SHA-256 / d7b67943c502d7a7ce727f447c597e16999b0779e65e8d5f6e1feac024f63171

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(events):
    cap=0; state='idle'; out=[]
    for e in events:
        if e[0]=='begin': cap=e[1]; state='running'
        elif e[0]=='fail':
            if state!='running': out.append('ignored'); continue
            if not e[1]: state='failed'; out.append('fatal'); continue
            if cap==0: state='failed'; out.append('exhausted'); continue
            if not e[2]: state='failed'; out.append('hook-error'); continue
            cap-=1; state='waiting'; out.append('scheduled')
        elif e[0]=='wake':
            if state=='waiting': state='running'; out.append('started')
        elif e[0]=='success':
            if state!='running': out.append('ignored'); continue
            state='done'; out.append('success')
            if not e[1]: out.append('observer-error')
        elif e[0]=='cancel': state='cancelled'
    return [out,cap,state]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('0', solve([]), [[],0,'idle'])
check('1', solve([('begin',2),('fail',True,True),('fail',True,True),('wake',),('success',False),('fail',True,True)]), [['scheduled','ignored','started','success','observer-error','ignored'],1,'done'])
check('2', solve([('begin',N),('fail',True,False),('wake',)]), [['hook-error'],N,'failed'])
check('3', solve([('begin',N),('fail',False,False)]), [['fatal'],N,'failed'])
check('4', solve([('begin',0),('fail',True,False)]), [['exhausted'],0,'failed'])
check('5', solve([('begin',N),('success',True),('success',False)]), [['success','ignored'],N,'done'])
check('6', solve([('begin',N),('fail',True,True),('cancel',),('wake',),('success',False)]), [['scheduled','ignored'],N-1,'cancelled'])
check('7', solve([('begin',N),('fail',False,True)]), [['fatal'],N,'failed'])
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
0[[], 0, 'idle'][[], 0, 'idle']Passed
1[['scheduled', 'ignored', 'started', 'success', 'observer-error', 'ignored'], 1, 'done'][['scheduled', 'ignored', 'started', 'success', 'observer-error', 'ignored'], 1, 'done']Passed
2[['hook-error'], 1, 'failed'][['hook-error'], 1, 'failed']Passed
3[['fatal'], 1, 'failed'][['fatal'], 1, 'failed']Passed
4[['exhausted'], 0, 'failed'][['exhausted'], 0, 'failed']Passed
5[['success', 'ignored'], 1, 'done'][['success', 'ignored'], 1, 'done']Passed
6[['scheduled', 'ignored'], 0, 'cancelled'][['scheduled', 'ignored'], 0, 'cancelled']Passed
7[['fatal'], 1, 'failed'][['fatal'], 1, 'failed']Passed

SHA-256 / 06065f51f00d5003576e0940508b0051d6a7ffa05b1aec0009a7e3582d63776a

Verification & scope

Stipulated bounded simulator, not a complete HTTP implementation or a standards conformance claim. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:41:56.511971+00:00.

Case digest / 78045cca77957ae47aa579ce53c6a7a184b9a7b5cc53df03e7332a47b822725a