Standard: when adding error recovery, retries, or asynchronous work, preserve invariants by (1) wiring every failure into the owning state machine, (2) keeping retry/timeout attribution explicit and conservative, and (3) ensuring UI/state changes made on error remain repairable.
Apply: 1) Enforce post-condition invariants (no fallthrough)
await ensureSessionPersisted(liveSession);
if (!liveSession.artifacts) {
// Invariant broken: continuing can orphan a running agent.
throw new Error(`Session ${requestedSessionId} persisted without artifacts`);
}
2) Never leave detached Promises or exception paths unhandled
await the async operation inside the try/catch that owns the error semantics, or attach a .catch() that routes into your failure handler.// Bad: unhandled rejection can break chat state/UI.
checkpointTracker.commit().then(async () => say('checkpoint_created'));
// Good: keep failure in the same control flow.
try {
await say('checkpoint_created');
const commitHash = await checkpointTracker.commit();
await updateCheckpoint(commitHash);
} catch (err) {
logAndHandleCheckpointFailure(err); // same intent as previous behavior
}
attach() are also caught (use .then(fn).catch(fn) rather than only .then(fn, onError)).3) Retry/timeout classification must be explicit
4) Don’t flip terminal UI/session state for errors that the runtime can’t repair
5) Telemetry and logging must not interrupt core execution
6) Preserve completed side effects across retries/stream failures
Quick checklist for PRs in this category