diff --git a/embassy/Cargo.toml b/embassy/Cargo.toml
index 99840e7e477f802bded59a72c2ad269b238b7907..56348ec25b01f6745653ab96807f2a8641e8c0e2 100644
--- a/embassy/Cargo.toml
+++ b/embassy/Cargo.toml
@@ -22,3 +22,7 @@ defmt = { version = "0.3", optional = true }
 
 [features]
 defmt = ["dep:defmt"]
+
+# Remove any use of `unsafe`. This currently requires
+# nightly and -Zpolonius (not ready yet)
+try-polonius = []
diff --git a/embassy/src/embassy_sunset.rs b/embassy/src/embassy_sunset.rs
index 3fd31c99e030e29f66308bf652887941d1ada727..f0221b25e56a539b1575bedf57a67c0d313dd775 100644
--- a/embassy/src/embassy_sunset.rs
+++ b/embassy/src/embassy_sunset.rs
@@ -301,48 +301,79 @@ impl<'a> EmbassySunset<'a> {
         Ok(())
     }
 
+    /// Returns an `Event` once one is ready.
+    ///
+    /// The returned `Event` borrows from the mutex locked in `ph`.
     pub(crate) async fn progress<'g, 'f>(&'g self, ph: &'f mut ProgressHolder<'g, 'a>) 
         -> Result<Event<'f, 'a>>
     {
+        let guard = &mut ph.g;
+        *guard = None;
+
+        #[cfg(not(feature = "try-polonius"))]
+        let guardptr = guard as *mut Option<MutexGuard<'g, SunsetRawMutex, Inner<'a>>>;
 
-        // poll until we get an actual event to return
-        let phcopy = ph as *mut ProgressHolder;
+        // poll progress until we get an actual event to return
         loop {
-            // TODO where does this go?
+            debug_assert!(guard.is_none());
+
+            // Safety: At the start of the loop iteration nothing is borrowing from
+            // guard, it is set to None. We dereference through a pointer to lose the 'f
+            // bound which applies to the Event::Cli/Event::Serv returned variants,
+            // but not other match arms.
+            //
+            // Once polonius is implemented this is unnecessary. polonius-the-crab
+            // can't be used since it would require an async closure.
+            #[cfg(not(feature = "try-polonius"))]
+            let guard = unsafe { &mut *guardptr };
+
             if self.exit.load(Relaxed) {
                 return Ok(Event::Defunct);
             }
 
-            // let ph = unsafe { &mut *phcopy };
-            ph.g = None;
-            let inner = ph.g.insert(self.inner.lock().await);
-            self.clear_refcounts(inner)?;
-
-            let ev = inner.runner.progress()?;
-            trace!("ev {ev:?}");
-            match ev {
-                Event::Cli(_) => return Ok(ev),
-                Event::Serv(_) => return Ok(ev),
-                Event::Progressed => continue,
-                Event::None => (),
-                _ => todo!("probably a bug"),
-            }
+            let idle = {
+                let inner = guard.insert(self.inner.lock().await);
 
-            // Idle until input is received
+                // Drop any finished channels now we have the lock
+                self.clear_refcounts(inner)?;
+
+                let ev = inner.runner.progress()?;
+                trace!("ev {ev:?}");
+
+                match ev {
+                    // Return borrowed Cli/Serv directly, with a Event<'f, 'a> bound.
+                    Event::Cli(_) => return Ok(ev),
+                    Event::Serv(_) => return Ok(ev),
+                    Event::Progressed => false,
+                    Event::None => true,
+                    _ => todo!("probably a bug"),
+                }
+            };
+
+            // Safety: No borrows of guard remain, can lose the inferred 'f lifetime.
+            // Not required after polonius.
+            #[cfg(not(feature = "try-polonius"))]
+            let guard = unsafe { &mut *guardptr };
+
+            // Drop the Mutex
+            *guard = None;
 
             // self.wake_channels(inner)?;
             if self.moribund.load(Relaxed) {
                 // if we're flushing, we exit once there is no progress
                 return Ok(Event::Defunct);
             }
+
+            if !idle {
+                // Run runner.progress() again if we made forward progress.
+                continue;
+            }
+
+            // Idle until input is received
             // TODO do we also want to wake in other situations?
             trace!("idle");
-            // drop the mutex while we idle.
-            // let ph = unsafe { &mut *phcopy };
-            ph.g = None;
             self.progress_notify.wait().await;
             trace!("woke");
-            // TODO: ProgressHolder should be Option<mutex>
         }
     }
 
diff --git a/embassy/src/lib.rs b/embassy/src/lib.rs
index 6fdf1f628dd77a9e6770c2e1aa2ff9a46c711f86..503efa12fd4c8263e4c932401a1352aaeea7fef2 100644
--- a/embassy/src/lib.rs
+++ b/embassy/src/lib.rs
@@ -1,6 +1,6 @@
 #![no_std]
 
-// #![forbid(unsafe_code)]
+#![cfg_attr(feature = "try-polonius", forbid(unsafe_code))]
 
 // avoid mysterious missing awaits
 #![deny(unused_must_use)]