diff --git a/server/index.js b/server/index.js
index f4c9e25e..d957ef58 100755
--- a/server/index.js
+++ b/server/index.js
@@ -76,6 +76,19 @@ const __dirname = getModuleDir(import.meta.url);
// Resolving the app root once keeps every repo-level lookup below aligned across both layouts.
const APP_ROOT = findAppRoot(__dirname);
const installMode = fs.existsSync(path.join(APP_ROOT, '.git')) ? 'git' : 'npm';
+// Version of the code that is actually running, captured once at process
+// startup. This intentionally does NOT re-read package.json per request: after
+// an update replaces the files on disk, package.json reflects the NEW version
+// while this long-lived process still runs the OLD code. The frontend bundle is
+// rebuilt on update, so a mismatch between this value and the frontend's
+// build-time version means the server was updated but not restarted.
+const RUNNING_VERSION = (() => {
+ try {
+ return JSON.parse(fs.readFileSync(path.join(APP_ROOT, 'package.json'), 'utf8')).version || null;
+ } catch {
+ return null;
+ }
+})();
const MAX_FILE_UPLOAD_SIZE_MB = 200;
const MAX_FILE_UPLOAD_SIZE_BYTES = MAX_FILE_UPLOAD_SIZE_MB * 1024 * 1024;
const MAX_FILE_UPLOAD_COUNT = 20;
@@ -156,7 +169,8 @@ app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
- installMode
+ installMode,
+ version: RUNNING_VERSION
});
});
diff --git a/src/components/sidebar/view/Sidebar.tsx b/src/components/sidebar/view/Sidebar.tsx
index 15d96990..5e544b08 100644
--- a/src/components/sidebar/view/Sidebar.tsx
+++ b/src/components/sidebar/view/Sidebar.tsx
@@ -43,7 +43,7 @@ function Sidebar({
}: SidebarProps) {
const { t } = useTranslation(['sidebar', 'common']);
const { isPWA } = useDeviceSettings({ trackMobile: false });
- const { updateAvailable, latestVersion, currentVersion, releaseInfo, installMode } = useVersionCheck(
+ const { updateAvailable, restartRequired, latestVersion, currentVersion, releaseInfo, installMode } = useVersionCheck(
'siteboon',
'claudecodeui',
);
@@ -224,6 +224,7 @@ function Sidebar({
onExpand={handleExpandSidebar}
onShowSettings={onShowSettings}
updateAvailable={updateAvailable}
+ restartRequired={restartRequired}
onShowVersionModal={() => setShowVersionModal(true)}
t={t}
/>
@@ -296,6 +297,7 @@ function Sidebar({
onCreateProject={() => setShowNewProject(true)}
onCollapseSidebar={handleCollapseSidebar}
updateAvailable={updateAvailable}
+ restartRequired={restartRequired}
releaseInfo={releaseInfo}
latestVersion={latestVersion}
currentVersion={currentVersion}
diff --git a/src/components/sidebar/view/subcomponents/SidebarCollapsed.tsx b/src/components/sidebar/view/subcomponents/SidebarCollapsed.tsx
index 90a6338f..c4ae4300 100644
--- a/src/components/sidebar/view/subcomponents/SidebarCollapsed.tsx
+++ b/src/components/sidebar/view/subcomponents/SidebarCollapsed.tsx
@@ -1,4 +1,4 @@
-import { Settings, Sparkles, PanelLeftOpen, Bug } from 'lucide-react';
+import { Settings, Sparkles, PanelLeftOpen, Bug, AlertTriangle } from 'lucide-react';
import type { TFunction } from 'i18next';
const DISCORD_INVITE_URL = 'https://discord.gg/buxwujPNRE';
@@ -16,6 +16,7 @@ type SidebarCollapsedProps = {
onExpand: () => void;
onShowSettings: () => void;
updateAvailable: boolean;
+ restartRequired: boolean;
onShowVersionModal: () => void;
t: TFunction;
};
@@ -24,6 +25,7 @@ export default function SidebarCollapsed({
onExpand,
onShowSettings,
updateAvailable,
+ restartRequired,
onShowVersionModal,
t,
}: SidebarCollapsedProps) {
@@ -75,6 +77,18 @@ export default function SidebarCollapsed({
+ {/* Restart-required indicator */}
+ {restartRequired && (
+
+ )}
+
{/* Update indicator */}
{updateAvailable && (