diff --git a/src/web/public/app.js b/src/web/public/app.js
index 1a9ddde5..8f3fbf4d 100644
--- a/src/web/public/app.js
+++ b/src/web/public/app.js
@@ -1794,8 +1794,10 @@ class CodemanApp {
}
});
- // Restore tabs that were open before refresh but are no longer on the server
- this._restoreEndedTabs();
+ // Server is source of truth for open sessions — don't resurrect stale tabs
+ // from localStorage (would show phantom "ended" tabs when a session was closed
+ // on another device).
+ try { localStorage.removeItem('codeman-tab-meta'); } catch {}
// Sync sessionOrder with current sessions (preserve order, add new, remove stale)
this.syncSessionOrder();
@@ -2117,8 +2119,7 @@ class CodemanApp {
const tallTabsEnabled = this._tallTabsEnabled ?? false;
const showFolder = tallTabsEnabled && session.name && folderName && folderName !== name;
- const endedAttr = session._ended ? ' data-ended="1"' : '';
- parts.push(`
+ parts.push(`
${_tabIdx < 9 ? '' + (_tabIdx + 1) + '' : ''}
@@ -2138,9 +2139,6 @@ class CodemanApp {
container.innerHTML = parts.join('');
- // Persist tab metadata for refresh recovery
- this._saveTabMetadata();
-
// Set up drag-and-drop handlers for tab reordering
this.setupTabDragHandlers();
@@ -2240,33 +2238,6 @@ class CodemanApp {
}
}
- // Save tab metadata to localStorage so ended sessions can be restored after refresh
- _saveTabMetadata() {
- try {
- const meta = {};
- for (const [id, s] of this.sessions) {
- if (s._ended) continue; // Don't persist ended stubs back
- meta[id] = { id, name: s.name || '', workingDir: s.workingDir || '', mode: s.mode || 'claude', color: s.color || 'default' };
- }
- localStorage.setItem('codeman-tab-meta', JSON.stringify(meta));
- } catch { /* ignore */ }
- }
-
- // Restore tabs that were open before refresh but are no longer on the server
- _restoreEndedTabs() {
- try {
- const saved = localStorage.getItem('codeman-tab-meta');
- if (!saved) return;
- const meta = JSON.parse(saved);
- for (const [id, info] of Object.entries(meta)) {
- if (!this.sessions.has(id)) {
- // Add a stub session so the tab renders
- this.sessions.set(id, { id, name: info.name, workingDir: info.workingDir, mode: info.mode, color: info.color, status: 'ended', _ended: true });
- }
- }
- } catch { /* ignore */ }
- }
-
// Set up drag-and-drop handlers on tab elements
setupTabDragHandlers() {
const container = this.$('sessionTabs');
@@ -2546,16 +2517,9 @@ class CodemanApp {
// Check if this is a restored session that needs to be attached
const session = this.sessions.get(sessionId);
- // Ended tabs (restored from localStorage, no longer on server) — show message, skip buffer load
- if (session?._ended) {
- this.terminal.clear();
- this.terminal.write('\r\n \x1b[2mSession ended. Close tab or click to reopen.\x1b[0m\r\n');
- return;
- }
-
// Track working directory for path normalization in Project Insights
this.currentSessionWorkingDir = session?.workingDir || null;
- if (session && session.pid === null && !session._ended) {
+ if (session && session.pid === null) {
// Session has no PTY attached — either restored after server restart
// or detached for some other reason. Re-attach regardless of status.
try {
diff --git a/src/web/public/styles.css b/src/web/public/styles.css
index ac331541..b8bc0355 100644
--- a/src/web/public/styles.css
+++ b/src/web/public/styles.css
@@ -293,7 +293,6 @@ body {
}
.session-tab .tab-status.error { background: var(--red); }
.session-tab .tab-status.ended { background: var(--text-muted); opacity: 0.5; }
-.session-tab[data-ended] { opacity: 0.55; }
/* Session color coding - left border indicator */
.session-tab[data-color="red"] { border-left: 3px solid var(--session-red); }
diff --git a/src/web/public/terminal-ui.js b/src/web/public/terminal-ui.js
index df931cf6..c70f4496 100644
--- a/src/web/public/terminal-ui.js
+++ b/src/web/public/terminal-ui.js
@@ -444,7 +444,6 @@ Object.assign(CodemanApp.prototype, {
if (
activeResizeSession &&
activeResizeSession.mode !== 'shell' &&
- !activeResizeSession._ended &&
this.terminal &&
this.isTerminalAtBottom()
) {