Oh My Pi extension: calling pi.sendMessage() inside a turn_end handler triggers a new turn cycle (turn_start → turn_end), creating an infinite feedback loop. Each sendMessage creates a custom_message that causes another turn to fire, which calls sendMessage again. The turnIndex dedup approach doesn't work because each new turn gets a fresh turnIndex. A setTimeout(0) guard doesn't work because the events fire asynchronously after the timeout clears. Clearing the guard flag in turn_start doesn't work because sendMessage also triggers turn_start, which clears the flag before the fake turn_end arrives.
Use a re-entrancy guard flag with a specific clearing strategy: set emitting = true before calling sendMessage(). In the turn_start handler, if emitting is true, return early (skip the fake turn_start). In the turn_end handler, if emitting is true, set emitting = false and return (consume the fake turn_end). This way the flag stays set through the entire fake turn cycle (fake turn_start → fake turn_end) and only clears when the fake turn_end is consumed. The next real turn_start sees emitting=false and proceeds normally.
let emitting = false;
pi.on('turn_start', async (event) => {
if (emitting) return; // skip fake turn_start from sendMessage
turnStartTime = event.timestamp ?? Date.now();
});
pi.on('turn_end', async (event, ctx) => {
if (emitting) { emitting = false; return; } // consume fake turn_end
// ... real turn_end logic ...
emitting = true;
pi.sendMessage({ customType: 'my-type', content: text, display: true });
});