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

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