When handling failures, treat “what happened” as a precise, testable contract—not an interpretation. Ensure (1) classification predicates are exact and consistent across docs+code, (2) failure causes are not overloaded, (3) cleanup/settlement invariants prevent stranded states, and (4) adapter/step contracts are enforced with safe fallbacks.
Apply these rules:
1) Freeze exact recovery predicates (spec == code)
2) Carry failure cause explicitly; never infer from an overloaded signal
3) Enforce exactly-once settlement and all parallel invariants
4) Validate “handled”/“presented” contracts; on violation, fall back to safe path
handled but didn’t perform the required settlement action (e.g., calling respond()), treat it as unsupported and continue the normal fallback so the request cannot get stuck.Example (pseudocode)
// 1) Exact error classification predicate
function classifyNoMatches(rg: { exitCode: number; stdout: string; stderr: string }) {
// NO MATCHES only when exitCode==1 and stderr is empty.
// (Do not key off stdout; JSON modes may emit stdout summaries.)
if (rg.exitCode === 1 && rg.stderr.length === 0) return 'no_matches';
return 'not_no_matches';
}
// 2) Adapter contract enforcement
async function presentWithContract(ctx: {
presentUserInputRequest: () => Promise<'presented'|'handled'|'unsupported'>;
respondMustBeCalledForHandled: () => void;
}) {
let respondCalled = false;
const wrappedRespond = () => { respondCalled = true; ctx.respondMustBeCalledForHandled(); };
const result = await ctx.presentUserInputRequest(/* uses wrappedRespond */);
if (result === 'handled' && !respondCalled) {
// Contract broken: fall through to existing permission formatter/sender.
return 'unsupported';
}
return result;
}
// 3) Typed settlement reasons (conceptual)
// settlementSignal.reason must distinguish answered_elsewhere vs request_cancelled/run_cancelled/expired.
Checklist before merge
Enter the URL of a public GitHub repository