When implementing async/concurrent behavior, prevent stale updates, duplicate side-effects, and resource leaks by following these rules:
1) Register wiring once; route through mutable state
2) Supersede correctly across awaits
3) Claim shared state before awaiting
4) Don’t re-discover mutable targets later
5) Serialize dependent workflows with a queue/scheduler
6) Cleanup resources once the promise settles
Example patterns
A) Listener registered once; mutable slot routes data
// register once
term.onData((data) => tab.pty?.write(data));
term.onResize(({ cols, rows }) => tab.pty?.resize(cols, rows));
// later, spawn toggles tab.pty / status
async function spawn(tab: TerminalTab) {
if (tab.status !== "new") return;
tab.status = "starting";
tab.pty = { write: () => {/* invoke hub */}, resize: () => {/* invoke */} };
try {
await doSpawn();
if (tab.status !== "starting") return; // invalidated during await
tab.status = "running";
} catch {
tab.status = "exited";
tab.pty = null;
}
}
B) Claim pending resolver before awaiting delivery
const resolve = pendingAskResolve;
if (!resolve) return false;
pendingAskResolve = undefined; // claim immediately
const hasAttachments = images?.length || files?.length;
if (hasAttachments && deliverAttachments) {
await deliverAttachments(() => /* still current */ true);
// core will echo/handle; don’t allow another resolve
}
setPhase("streaming");
resolve(prompt ?? "");
return true;
C) Capture the exact target before slow async work
const idx = findLastIndex(messages, m => m.say === "checkpoint_created");
await say("checkpoint_created");
const targetIdx = idx; // capture synchronously after message is created
const commitHash = await checkpointTracker.commit();
if (commitHash && targetIdx !== -1) {
await updateClineMessage(targetIdx, { lastCheckpointHash: commitHash });
}
Use this checklist during review: if a feature involves retries, cancellation, replacement, background tasks, polling+actions, or multi-client state, explicitly document how you prevent (1) duplicate side-effects, (2) stale writes after await, and (3) leaked resources after settlement.