diff --git a/api/kanban_bridge.py b/api/kanban_bridge.py
index 403f2710..d758d372 100644
--- a/api/kanban_bridge.py
+++ b/api/kanban_bridge.py
@@ -745,6 +745,18 @@ def _list_boards_payload(parsed):
current = kb.get_current_board()
except Exception:
current = "default"
+ visible_slugs = {(_board_meta_dict(meta).get("slug")) for meta in boards}
+ default_slug = getattr(kb, "DEFAULT_BOARD", "default")
+ if current not in visible_slugs:
+ # The on-disk active-board pointer can outlive an archived/deleted board
+ # when another CLI/WebUI process removes it. Surface a valid current
+ # board instead of letting the frontend pin every subsequent request to
+ # a ghost slug and fail with an opaque 404.
+ try:
+ kb.clear_current_board()
+ except Exception:
+ pass
+ current = default_slug
out = []
for raw_meta in boards:
meta = _board_meta_dict(raw_meta)
diff --git a/api/routes.py b/api/routes.py
index e086587a..18d9c1a5 100644
--- a/api/routes.py
+++ b/api/routes.py
@@ -590,6 +590,19 @@ from api.agent_health import build_agent_health_payload
from api.system_health import build_system_health_payload
+def _kanban_unknown_endpoint(handler, parsed, method: str) -> bool:
+ """Return a Kanban-specific 404 for stale clients/obsolete endpoint shapes."""
+ return bad(
+ handler,
+ (
+ f"unknown Kanban endpoint: {method} {parsed.path}. "
+ "If this appeared after a WebUI update, your browser may be running "
+ "a stale cached bundle; use Hard refresh now, then reopen Kanban."
+ ),
+ status=404,
+ ) or True
+
+
def _clear_stale_stream_state(session) -> bool:
"""Clear persisted streaming flags when the in-memory stream no longer exists.
@@ -2616,7 +2629,9 @@ def handle_get(handler, parsed) -> bool:
if parsed.path.startswith("/api/kanban/"):
from api.kanban_bridge import handle_kanban_get
- return handle_kanban_get(handler, parsed)
+ if handle_kanban_get(handler, parsed):
+ return True
+ return _kanban_unknown_endpoint(handler, parsed, "GET")
if parsed.path == "/api/wiki/status":
return _handle_llm_wiki_status(handler, parsed)
if parsed.path == "/api/logs":
@@ -3414,7 +3429,9 @@ def handle_post(handler, parsed) -> bool:
if parsed.path.startswith("/api/kanban/"):
from api.kanban_bridge import handle_kanban_post
- return handle_kanban_post(handler, parsed, body)
+ if handle_kanban_post(handler, parsed, body):
+ return True
+ return _kanban_unknown_endpoint(handler, parsed, "POST")
if parsed.path == "/api/dashboard/config":
from api import dashboard_probe
@@ -4590,7 +4607,9 @@ def handle_patch(handler, parsed) -> bool:
if parsed.path.startswith("/api/kanban/"):
from api.kanban_bridge import handle_kanban_patch
- return handle_kanban_patch(handler, parsed, body)
+ if handle_kanban_patch(handler, parsed, body):
+ return True
+ return _kanban_unknown_endpoint(handler, parsed, "PATCH")
return False
@@ -4602,7 +4621,9 @@ def handle_delete(handler, parsed) -> bool:
if parsed.path.startswith("/api/kanban/"):
from api.kanban_bridge import handle_kanban_delete
- return handle_kanban_delete(handler, parsed, body)
+ if handle_kanban_delete(handler, parsed, body):
+ return True
+ return _kanban_unknown_endpoint(handler, parsed, "DELETE")
return False
# ── GET route helpers ─────────────────────────────────────────────────────────
diff --git a/docs/pr-media/1823/kanban-hard-refresh-diagnostic.png b/docs/pr-media/1823/kanban-hard-refresh-diagnostic.png
new file mode 100644
index 00000000..94546c3e
Binary files /dev/null and b/docs/pr-media/1823/kanban-hard-refresh-diagnostic.png differ
diff --git a/static/panels.js b/static/panels.js
index 9b056382..0add57b8 100644
--- a/static/panels.js
+++ b/static/panels.js
@@ -1189,11 +1189,49 @@ function _kanbanCard(task, status){
`;
}
+async function hardRefreshWebUIClient(){
+ try {
+ if (navigator.serviceWorker) {
+ const regs = await navigator.serviceWorker.getRegistrations();
+ await Promise.all(regs.map(r => r.unregister()));
+ }
+ } catch(_) {}
+ try {
+ if (window.caches) {
+ const keys = await caches.keys();
+ await Promise.all(keys.map(k => caches.delete(k)));
+ }
+ } catch(_) {}
+ window.location.reload();
+}
+
+function _kanbanLooksLikeStaleClientError(err){
+ const msg = String((err && err.message) || err || '').toLowerCase();
+ return !!(err && err.status === 404 && (
+ msg === 'not found' ||
+ msg.includes('unknown kanban endpoint') ||
+ msg.includes('stale cached bundle')
+ ));
+}
+
+function _kanbanUnavailableHtml(err){
+ const raw = String((err && err.message) || err || '');
+ if (_kanbanLooksLikeStaleClientError(err)) {
+ return `
Kanban needs a hard refresh
The server rejected an obsolete Kanban endpoint. This usually means the browser or Mac app is still running a stale cached WebUI bundle after an update.