diff --git a/plugins/github/server.ts b/plugins/github/server.ts
index 2daeebe8d..bb4025007 100644
--- a/plugins/github/server.ts
+++ b/plugins/github/server.ts
@@ -12,6 +12,10 @@ import { defineRpcContract, type BbPluginApi } from "@get-bb/plugin-sdk";
 import { z } from "zod";
 
 const SYNC_INTERVAL_MS = 5 * 60_000;
+// Retry cadence while `gh auth status` fails for a reason that is not a
+// configuration problem (network blip, locked keychain, slow host): start at
+// 30 s and back off to the regular sync interval.
+const SYNC_RETRY_BASE_MS = 30_000;
 const ISSUE_PAGE = 100;
 const CLOSED_ISSUE_PAGE = 50;
 const PR_PAGE = 50;
@@ -310,6 +314,10 @@ function needsConfiguration(message: string): Error {
   });
 }
 
+function isNeedsConfigurationError(error: unknown): error is Error {
+  return error instanceof Error && error.name === "NeedsConfigurationError";
+}
+
 /** owner/name from any GitHub remote URL (https, ssh, git@), else null. */
 export function parseGithubRemote(url: string): string | null {
   const match = url
@@ -497,14 +505,37 @@ export default async function plugin(bb: BbPluginApi) {
     return stdout;
   }
 
+  // `gh auth status` is a network probe (it calls the GitHub API), so a
+  // failure does not by itself mean gh is unconfigured. Only two outcomes
+  // are configuration problems worth latching needs-configuration on:
+  // gh missing, and gh present but holding no credentials at all
+  // (`gh auth token` is local-only, so it answers that without the network).
+  // Anything else (network down, keychain locked, slow host, timeout) is
+  // reported as a plain error so callers retry instead of latching.
   async function checkAuth(): Promise<void> {
     try {
       await gh(["auth", "status"], 10_000);
       ghAuthError = null;
+      return;
     } catch (error) {
       ghAuthError = error instanceof Error ? error.message : String(error);
+      if (isNeedsConfigurationError(error)) throw error; // gh not found
+    }
+    let hasToken = true;
+    try {
+      await gh(["auth", "token"], 5_000);
+    } catch (error) {
+      // Only gh's own "no credentials" answer is a configuration problem; a
+      // timeout or a crash of the local check is treated as transient too.
+      const message = error instanceof Error ? error.message : String(error);
+      hasToken = !/no oauth token|not logged in/i.test(message);
+    }
+    if (!hasToken) {
       throw needsConfiguration(`GitHub CLI is not authenticated. ${GH_HINT}`);
     }
+    throw new Error(
+      `gh auth status failed; gh has credentials, so this is probably transient and will be retried: ${ghAuthError}`,
+    );
   }
 
   // ------------------------------------------------------------------
@@ -730,10 +761,27 @@ export default async function plugin(bb: BbPluginApi) {
   // instead of crash-looping.
   bb.background.service("sync", {
     async start(signal) {
+      let failures = 0;
       while (!signal.aborted) {
-        await syncAll();
+        let delayMs = SYNC_INTERVAL_MS;
+        try {
+          await syncAll();
+          failures = 0;
+        } catch (error) {
+          if (isNeedsConfigurationError(error)) throw error;
+          failures += 1;
+          delayMs = Math.min(
+            SYNC_RETRY_BASE_MS * 2 ** (failures - 1),
+            SYNC_INTERVAL_MS,
+          );
+          bb.log.warn(
+            `sync failed (retry in ${Math.round(delayMs / 1000)}s): ${
+              error instanceof Error ? error.message : String(error)
+            }`,
+          );
+        }
         await new Promise<void>((resolve) => {
-          const timer = setTimeout(resolve, SYNC_INTERVAL_MS);
+          const timer = setTimeout(resolve, delayMs);
           signal.addEventListener(
             "abort",
             () => {
@@ -752,9 +800,11 @@ export default async function plugin(bb: BbPluginApi) {
   try {
     await checkAuth();
   } catch (error) {
-    bb.status.needsConfiguration(
-      error instanceof Error ? error.message : String(error),
-    );
+    if (isNeedsConfigurationError(error)) {
+      bb.status.needsConfiguration(error.message);
+    } else {
+      bb.log.warn(error instanceof Error ? error.message : String(error));
+    }
   }
 
   // ------------------------------------------------------------------
@@ -907,6 +957,15 @@ export default async function plugin(bb: BbPluginApi) {
   bb.rpc.register(githubRpcContract, {
     /** () → auth/sync status for the panel banner. */
     async status() {
+      // Re-probe on demand after a failed probe so a recovered gh is noticed
+      // the next time the panel asks, not only on the next sync tick.
+      if (ghAuthError !== null) {
+        try {
+          await checkAuth();
+        } catch {
+          // ghAuthError already carries the failure
+        }
+      }
       const cursor = await bb.storage.kv.get<{
         lastSyncedAt: string;
         repos: number;
