diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html
index 3c36d0b..9c2eaac 100644
--- a/web/zebra-spaces.html
+++ b/web/zebra-spaces.html
@@ -4236,6 +4236,11 @@ async function handleSignal(raw){
const prev = mm.role;
mm.role = m.role;
roomEpoch = m.epoch;
+ /* same gate-release as case 'state' / case 'peer-booted' —
+ * role-change carries a fresh epoch, so the next mod action
+ * (e.g. promote-then-kick) shouldn't have to wait the 1500ms
+ * timeout fallback before signing. */
+ resolvePendingStateUpdate();
if (m.role === 'host') hostUUID = m.uuid;
const subjPub = pubHexFromMsg(m, 'pubkey', 'pubkey_hex') || myKeys && (m.uuid===myUUID ? myKeys.pubHex : '');
const byPub = pubHexFromMsg(m, 'by_pubkey');
@@ -4314,6 +4319,15 @@ async function handleSignal(raw){
break;
case 'peer-booted':
{
+ /* Server bumps room epoch on every kick/ban and includes it in
+ * this broadcast. Pick it up + release the mod-action queue
+ * gate so the next kick signs against the fresh epoch — without
+ * this the second kick in a session hits "stale epoch". Fox
+ * 2026-06-04: "couldn't kick until leaving as host." */
+ if (typeof m.epoch === 'number') {
+ roomEpoch = m.epoch;
+ resolvePendingStateUpdate();
+ }
const mm = members.get(m.uuid);
const by = members.get(m.by);
const victPub = pubHexFromMsg(m, 'pubkey')
@@ -5870,8 +5884,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');