Skip to content

feat: implement session rename with SQLite storage#413

Merged
blackmammoth merged 6 commits into
siteboon:mainfrom
PaloSP:fix/session-rename
Mar 3, 2026
Merged

feat: implement session rename with SQLite storage#413
blackmammoth merged 6 commits into
siteboon:mainfrom
PaloSP:fix/session-rename

Conversation

@PaloSP
Copy link
Copy Markdown
Contributor

@PaloSP PaloSP commented Feb 22, 2026

Summary

Changes

  • Backend: new session_names table, PUT /api/sessions/:sessionId/rename endpoint, sessionNamesDb helper with UPSERT, migration
  • Frontend: replaced stub updateSessionSummary with real API call, threaded provider param through sidebar component chain
  • Bug fix: renameProject() now uses spread merge { ...config[projectName], displayName } instead of overwriting entire config
  • i18n: added rename error messages (en, ja, ko, zh-CN)
  • Injection points: custom names applied at 8 points — both project loops (directory + manual) × 3 providers + lazy-load endpoint + Codex/Cursor independent routes

Why SQLite instead of .jsonl?

Claude CLI owns .jsonl session files and can overwrite custom names via auto-titling after /resume (see anthropics/claude-code#25090). Storing names in the existing SQLite DB avoids this entirely.

Test plan

  • Rename a Claude session — verify name persists after refresh
  • Rename a Codex/Cursor session — verify same
  • Rename a project — verify manuallyAdded and originalPath are preserved
  • Resume a session in Claude CLI — verify custom name is not overwritten
  • Lazy-load more sessions — verify custom names appear on loaded sessions
  • Empty rename — verify it's rejected (no blank names)

Summary by CodeRabbit

  • New Features

    • Users can rename sessions in the UI; custom names are stored server‑side and shown consistently across all providers and session lists.
    • Renames persist immediately and are applied when sessions are loaded; client and server APIs added to save and fetch custom names.
    • Session name entries are cleaned up when sessions are deleted.
  • Internationalization

    • Added localized rename error messages for English, Japanese, Korean, and Simplified Chinese.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 22, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3cdd067 and 47c269a.

📒 Files selected for processing (19)
  • server/database/db.js
  • server/database/init.sql
  • server/index.js
  • server/projects.js
  • server/routes/codex.js
  • server/routes/cursor.js
  • server/routes/gemini.js
  • src/components/sidebar/hooks/useSidebarController.ts
  • src/components/sidebar/utils/utils.ts
  • src/components/sidebar/view/Sidebar.tsx
  • src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx
  • src/components/sidebar/view/subcomponents/SidebarProjectList.tsx
  • src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx
  • src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx
  • src/i18n/locales/en/sidebar.json
  • src/i18n/locales/ja/sidebar.json
  • src/i18n/locales/ko/sidebar.json
  • src/i18n/locales/zh-CN/sidebar.json
  • src/utils/api.js

📝 Walkthrough

Walkthrough

Adds DB-backed, provider-aware session naming: new session_names table and DB API, server endpoint to rename sessions and apply custom names when listing sessions, frontend API and UI wiring to rename sessions, and i18n entries for rename errors.

Changes

Cohort / File(s) Summary
Database schema & DB API
server/database/init.sql, server/database/db.js
Add session_names table and index; implement sessionNamesDb with setName, getName, getNames, deleteName; export applyCustomSessionNames to map DB names onto in-memory sessions.
Server endpoints & wiring
server/index.js, server/projects.js, server/routes/...
server/routes/cursor.js, server/routes/codex.js, server/routes/gemini.js
New PUT /api/sessions/:sessionId/rename; call applyCustomSessionNames(..., provider) when returning sessions for each provider; delete corresponding name entries on session deletion.
Frontend API & callers
src/utils/api.js, src/components/sidebar/hooks/useSidebarController.ts, src/components/sidebar/view/Sidebar.tsx
Add api.renameSession(sessionId, summary, provider); implement updateSessionSummary to call API, validate/trim input, handle errors, and refresh; propagate provider through sidebar save callbacks.
Sidebar subcomponents
src/components/sidebar/view/subcomponents/SidebarProjectList.tsx, .../SidebarProjectItem.tsx, .../SidebarProjectSessions.tsx, .../SidebarSessionItem.tsx
Expand onSaveEditingSession prop to accept provider: SessionProvider; pass session.__provider at save sites; adjust inline-edit UI and action controls.
i18n
src/i18n/locales/en/sidebar.json, src/i18n/locales/ja/sidebar.json, src/i18n/locales/ko/sidebar.json, src/i18n/locales/zh-CN/sidebar.json
Add renameSessionFailed and renameSessionError localization keys.
Session name util
src/components/sidebar/utils/utils.ts
Prefer session.summary for cursor provider before falling back to session.name then untitled label.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant Frontend
    participant API as "Client API\n(src/utils/api.js)"
    participant Server
    participant DBAPI as "sessionNamesDb\n(server/database/db.js)"
    participant DB

    User->>Frontend: Edit session title + Save (provider)
    Frontend->>API: renameSession(sessionId, summary, provider)
    API->>Server: PUT /api/sessions/:sessionId/rename {summary, provider}
    Server->>DBAPI: sessionNamesDb.setName(sessionId, provider, summary)
    DBAPI->>DB: INSERT ... ON CONFLICT UPDATE custom_name, updated_at
    DB-->>DBAPI: OK
    DBAPI-->>Server: OK
    Server-->>API: { success: true }
    API-->>Frontend: success
    Frontend->>Server: GET /api/projects/:projectName/sessions
    Server->>DBAPI: sessionNamesDb.getNames([sessionIds], provider)
    DBAPI->>DB: SELECT session_id, custom_name FROM session_names WHERE session_id IN (...) AND provider=...
    DB-->>DBAPI: rows
    DBAPI-->>Server: Map(sessionId -> custom_name)
    Server->>Server: applyCustomSessionNames(sessions, provider)
    Server-->>Frontend: sessions (with custom_name applied to session.summary)
    Frontend-->>User: render renamed sessions
Loading

Possibly related PRs

Suggested reviewers

  • viper151

Poem

"🐇 I hopped to the DB to leave a bright name,
Per provider it waits — no two are the same.
Click, edit, confirm — the change holds true,
Sessions wear names that users construe.
A little rabbit cheers the rename crew!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main feature: implementing session rename functionality with SQLite storage as the persistence mechanism.
Linked Issues check ✅ Passed The PR successfully addresses both linked issues: #72 by implementing updateSessionSummary with a real API call and backend endpoint for persistence, and #358 by fixing project rename to merge configs preserving fields like manuallyAdded and originalPath.
Out of Scope Changes check ✅ Passed All code changes are within scope of the linked issues: database schema/API for session rename persistence, backend endpoints, frontend UI wiring, i18n additions, and project rename fix. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
server/routes/codex.js (1)

63-71: Duplicate custom-name injection pattern — consider extracting a shared helper.

The same 6-line block (collect IDs → getNames → assign summary) appears identically in cursor.js and, per the AI summaries, also in server/index.js and server/projects.js. Extracting it into a small utility (e.g., applyCustomSessionNames(sessions, provider) exported from db.js or a shared utils module) would reduce repetition.

♻️ Suggested helper
// server/database/db.js  (add alongside sessionNamesDb)
+export function applyCustomSessionNames(sessions, provider) {
+  if (!sessions?.length) return;
+  const ids = sessions.map(s => s.id);
+  const customNames = sessionNamesDb.getNames(ids, provider);
+  for (const session of sessions) {
+    const custom = customNames.get(session.id);
+    if (custom) session.summary = custom;
+  }
+}
// server/routes/codex.js
-import { sessionNamesDb } from '../database/db.js';
+import { applyCustomSessionNames } from '../database/db.js';
 ...
-    // Apply custom session names from DB
-    if (sessions?.length > 0) {
-      const ids = sessions.map(s => s.id);
-      const customNames = sessionNamesDb.getNames(ids, 'codex');
-      for (const session of sessions) {
-        const custom = customNames.get(session.id);
-        if (custom) session.summary = custom;
-      }
-    }
+    applyCustomSessionNames(sessions, 'codex');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/routes/codex.js` around lines 63 - 71, The repeated pattern that
collects session ids, calls sessionNamesDb.getNames(ids, 'codex'), and injects
the returned name into session.summary should be extracted into a single helper
(e.g., applyCustomSessionNames(sessions, provider)) exported from a shared
module (db.js or utils). Implement applyCustomSessionNames to accept the
sessions array and provider string, early-return if sessions is empty, map ids,
call sessionNamesDb.getNames(ids, provider), and assign custom names to each
session.summary; then replace the duplicate blocks in codex.js, cursor.js,
server/index.js and server/projects.js with a call to
applyCustomSessionNames(sessions, 'codex' /* or appropriate provider */).
server/projects.js (1)

568-568: Dead call — project.sessions is always [] here.

For manual projects, project.sessions is initialized to [] (line 559) and no Claude sessions are ever populated before this call. applyCustomSessionNames short-circuits on the empty guard and is a no-op. Consider removing it.

♻️ Proposed fix
-      applyCustomSessionNames(project.sessions, 'claude');
-
       // Try to fetch Cursor sessions for manual projects too
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/projects.js` at line 568, The call
applyCustomSessionNames(project.sessions, 'claude') is dead because
project.sessions is initialized to [] for manual projects and never populated
before this call; remove the no-op invocation (delete the
applyCustomSessionNames(...) line) or move it to a place after sessions are
actually populated (where project.sessions may be non-empty) — locate the call
to applyCustomSessionNames and either remove it or relocate it to the code path
that fills project.sessions so the function can run on a non-empty array.
src/components/sidebar/hooks/useSidebarController.ts (1)

404-404: projectName is declared but never used in the function body.

The rename API only needs sessionId and provider. If the parameter is kept for interface symmetry with onDeleteSession, prefix it with _ to communicate the intent and satisfy noUnusedParameters.

♻️ Proposed fix
-    async (projectName: string, sessionId: string, summary: string, provider: SessionProvider) => {
+    async (_projectName: string, sessionId: string, summary: string, provider: SessionProvider) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/sidebar/hooks/useSidebarController.ts` at line 404, The async
rename handler declared as async (projectName: string, sessionId: string,
summary: string, provider: SessionProvider) has an unused parameter projectName;
either remove it or rename it to _projectName to satisfy noUnusedParameters
while keeping interface symmetry with onDeleteSession. Update the function
signature in useSidebarController's rename handler (the async arrow function) to
use _projectName if you must preserve the parameter, or delete projectName and
adjust any callers/typing to only accept sessionId, summary, and provider.
server/index.js (2)

549-554: sessionId is unvalidated and summary has no length cap.

The token-usage endpoint at Line 1639 sanitizes sessionId with String(sessionId).replace(/[^a-zA-Z0-9._-]/g, '') before use. The rename endpoint skips this step entirely, which is inconsistent. While parameterized queries prevent SQL injection, applying the same sanitization pattern would keep the two code paths consistent. Similarly, a missing upper-bound on summary allows arbitrarily long strings to be stored.

♻️ Proposed fix
         const { sessionId } = req.params;
+        const safeSessionId = String(sessionId).replace(/[^a-zA-Z0-9._-]/g, '');
+        if (!safeSessionId) {
+            return res.status(400).json({ error: 'Invalid sessionId' });
+        }
         const { summary, provider } = req.body;
-        if (!summary || typeof summary !== 'string' || summary.trim() === '') {
+        const MAX_SUMMARY_LENGTH = 500;
+        if (!summary || typeof summary !== 'string' || summary.trim() === '') {
             return res.status(400).json({ error: 'Summary is required' });
         }
+        if (summary.trim().length > MAX_SUMMARY_LENGTH) {
+            return res.status(400).json({ error: `Summary must not exceed ${MAX_SUMMARY_LENGTH} characters` });
+        }
         ...
-        sessionNamesDb.setName(sessionId, provider, summary.trim());
+        sessionNamesDb.setName(safeSessionId, provider, summary.trim());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.js` around lines 549 - 554, The rename endpoint
(app.put('/api/sessions/:sessionId/rename')) is missing the same sessionId
sanitization used elsewhere and allows unbounded summary lengths; update the
handler to sanitize sessionId using
String(sessionId).replace(/[^a-zA-Z0-9._-]/g, '') before any DB use (replace
uses of sessionId with the sanitized variable) and validate summary to be a
non-empty trimmed string with an enforced max length (e.g., 200 characters),
returning res.status(400).json({ error: '...' }) when validation fails.

556-558: Validate provider against known values.

Any non-empty string passes the current check, so callers can persist entries like "foobar" that will never be queried, polluting session_names. Restrict to the three supported values.

♻️ Proposed fix
+        const VALID_PROVIDERS = ['claude', 'codex', 'cursor'];
         if (!provider || typeof provider !== 'string') {
             return res.status(400).json({ error: 'Provider is required' });
         }
+        if (!VALID_PROVIDERS.includes(provider)) {
+            return res.status(400).json({ error: `Provider must be one of: ${VALID_PROVIDERS.join(', ')}` });
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.js` around lines 556 - 558, The current check around the
provider variable (the if (!provider || typeof provider !== 'string') block)
allows any non-empty string; change it to validate provider against the explicit
set of supported values by replacing that condition with a membership check
(e.g. create an allowedProviders array of the three supported provider strings
and verify allowedProviders.includes(provider)); if the value is not in the
allowed set, return res.status(400).json with an error message that lists the
allowed providers so only the three supported values are accepted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/database/db.js`:
- Around line 79-92: The empty catch around the db.exec calls for creating
session_names and idx_session_names_lookup is swallowing real DB errors; remove
the inner try/catch so failures from db.exec(...) (creating session_names /
creating idx_session_names_lookup) propagate to the outer runMigrations error
handling, or if you prefer to keep a local catch, change it to catch (err) and
rethrow or log the error with context (e.g., include err and mention
"session_names/index creation") instead of silently ignoring it.

In `@server/projects.js`:
- Around line 71-79: The call to sessionNamesDb.getNames inside
applyCustomSessionNames can throw and currently bubbles up; wrap the getNames
call (or the whole body of applyCustomSessionNames) in a try/catch so DB errors
are caught, logged, and do not abort getProjects; if an error occurs, log via
the existing logger (or console.error) and simply return without modifying
sessions so processing continues. Ensure you reference applyCustomSessionNames
and sessionNamesDb.getNames when locating the change.

In `@server/routes/cursor.js`:
- Around line 568-570: getSessionName currently ignores Cursor session.summary
and always returns session.name; update the Cursor branch in
src/components/sidebar/utils/utils.ts inside the getSessionName function so it
returns session.summary || session.name || t('projects.untitledSession') instead
of session.name || t('projects.untitledSession'), ensuring custom renames are
used; locate the Cursor case (the branch that handles Cursor sessions) and
replace the return expression accordingly.

In `@src/components/sidebar/hooks/useSidebarController.ts`:
- Around line 403-423: updateSessionSummary currently clears edit state
(setEditingSession, setEditingSessionName) before awaiting api.renameSession,
which removes the input on failure; change it to perform those clears in a
finally block after the API call/logic (like saveProjectName does).
Specifically, in updateSessionSummary (the async callback that calls
api.renameSession and onRefresh), remove the synchronous
setEditingSession(null)/setEditingSessionName('') at the top and instead call
them inside a finally clause after the try/catch so the UI only clears when the
operation completes (success or failure), keeping api.renameSession, onRefresh
and the existing error handling intact.

---

Nitpick comments:
In `@server/index.js`:
- Around line 549-554: The rename endpoint
(app.put('/api/sessions/:sessionId/rename')) is missing the same sessionId
sanitization used elsewhere and allows unbounded summary lengths; update the
handler to sanitize sessionId using
String(sessionId).replace(/[^a-zA-Z0-9._-]/g, '') before any DB use (replace
uses of sessionId with the sanitized variable) and validate summary to be a
non-empty trimmed string with an enforced max length (e.g., 200 characters),
returning res.status(400).json({ error: '...' }) when validation fails.
- Around line 556-558: The current check around the provider variable (the if
(!provider || typeof provider !== 'string') block) allows any non-empty string;
change it to validate provider against the explicit set of supported values by
replacing that condition with a membership check (e.g. create an
allowedProviders array of the three supported provider strings and verify
allowedProviders.includes(provider)); if the value is not in the allowed set,
return res.status(400).json with an error message that lists the allowed
providers so only the three supported values are accepted.

In `@server/projects.js`:
- Line 568: The call applyCustomSessionNames(project.sessions, 'claude') is dead
because project.sessions is initialized to [] for manual projects and never
populated before this call; remove the no-op invocation (delete the
applyCustomSessionNames(...) line) or move it to a place after sessions are
actually populated (where project.sessions may be non-empty) — locate the call
to applyCustomSessionNames and either remove it or relocate it to the code path
that fills project.sessions so the function can run on a non-empty array.

In `@server/routes/codex.js`:
- Around line 63-71: The repeated pattern that collects session ids, calls
sessionNamesDb.getNames(ids, 'codex'), and injects the returned name into
session.summary should be extracted into a single helper (e.g.,
applyCustomSessionNames(sessions, provider)) exported from a shared module
(db.js or utils). Implement applyCustomSessionNames to accept the sessions array
and provider string, early-return if sessions is empty, map ids, call
sessionNamesDb.getNames(ids, provider), and assign custom names to each
session.summary; then replace the duplicate blocks in codex.js, cursor.js,
server/index.js and server/projects.js with a call to
applyCustomSessionNames(sessions, 'codex' /* or appropriate provider */).

In `@src/components/sidebar/hooks/useSidebarController.ts`:
- Line 404: The async rename handler declared as async (projectName: string,
sessionId: string, summary: string, provider: SessionProvider) has an unused
parameter projectName; either remove it or rename it to _projectName to satisfy
noUnusedParameters while keeping interface symmetry with onDeleteSession. Update
the function signature in useSidebarController's rename handler (the async arrow
function) to use _projectName if you must preserve the parameter, or delete
projectName and adjust any callers/typing to only accept sessionId, summary, and
provider.

Comment thread server/database/db.js Outdated
Comment thread server/projects.js Outdated
Comment thread server/routes/cursor.js Outdated
Comment thread src/components/sidebar/hooks/useSidebarController.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
server/database/db.js (1)

379-392: getNames defeats the prepared-statement cache and has a theoretical parameter-count ceiling.

Because the SQL string varies with sessionIds.length (each different count is a distinct string), db.prepare() produces a cache miss on every call with a new batch size. Additionally, spreading ...sessionIds, provider into .all() works only while sessionIds.length < SQLITE_MAX_VARIABLE_NUMBER (32766 with the bundled SQLite 3.46+); exceeding that would throw, and applyCustomSessionNames's catch would silently discard all custom names for the affected batch.

Consider chunking into fixed-size batches (e.g., 500) with a stable prepared statement per chunk:

♻️ Suggested refactor
+  const CHUNK_SIZE = 500;
+  const FETCH_STMT = db.prepare(
+    `SELECT session_id, custom_name FROM session_names
+     WHERE session_id IN (${Array(CHUNK_SIZE).fill('?').join(',')}) AND provider = ?`
+  );
+
   getNames: (sessionIds, provider) => {
     try {
       if (!sessionIds.length) return new Map();
-      const placeholders = sessionIds.map(() => '?').join(',');
-      const rows = db.prepare(
-        `SELECT session_id, custom_name FROM session_names
-         WHERE session_id IN (${placeholders}) AND provider = ?`
-      ).all(...sessionIds, provider);
+      const result = new Map();
+      for (let i = 0; i < sessionIds.length; i += CHUNK_SIZE) {
+        const chunk = sessionIds.slice(i, i + CHUNK_SIZE);
+        const stmt = chunk.length === CHUNK_SIZE
+          ? FETCH_STMT
+          : db.prepare(
+              `SELECT session_id, custom_name FROM session_names
+               WHERE session_id IN (${chunk.map(() => '?').join(',')}) AND provider = ?`
+            );
+        const rows = stmt.all(...chunk, provider);
+        rows.forEach(r => result.set(r.session_id, r.custom_name));
+      }
-      return new Map(rows.map(r => [r.session_id, r.custom_name]));
+      return result;
     } catch (err) {
       throw err;
     }
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/database/db.js` around lines 379 - 392, getNames currently builds a
different SQL string for every batch size and spreads sessionIds into .all(),
which busts the prepare cache and can exceed SQLITE_MAX_VARIABLE_NUMBER; change
getNames to process sessionIds in fixed-size chunks (e.g., 500) and query with a
stable prepared statement that accepts a single JSON array parameter (e.g.,
"WHERE session_id IN (SELECT value FROM json_each(?)) AND provider = ?"),
calling .all(JSON.stringify(chunk), provider) for each chunk and merging
results; update any callers like applyCustomSessionNames to rely on the new
getNames behavior so large batches are handled without cache misses or
parameter-count errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/index.js`:
- Around line 544-547: The code currently creates safeSessionId by stripping
disallowed chars and only rejects when empty, which can silently change a
valid-but-different session ID; instead validate that the sanitized value
exactly matches the original sessionId and return 400 on mismatch. In the
handler that computes safeSessionId (the sessionId param and safeSessionId
variable), compare String(sessionId) to the sanitized string and if they differ
respond with res.status(400).json({ error: 'Invalid sessionId' }) so no
renaming/lookup happens on a mutated ID.

---

Duplicate comments:
In `@server/database/db.js`:
- Around line 78-92: The inner try/catch around the session_names migration (the
db.exec calls that create the session_names table and idx_session_names_lookup)
is swallowing real DB errors; remove that inner try/catch so exceptions from
db.exec bubble up to the outer runMigrations handler (or, if you want to log,
call console.warn and then re-throw the error) so genuine failures (disk full,
permission denied, corruption) are not suppressed.

---

Nitpick comments:
In `@server/database/db.js`:
- Around line 379-392: getNames currently builds a different SQL string for
every batch size and spreads sessionIds into .all(), which busts the prepare
cache and can exceed SQLITE_MAX_VARIABLE_NUMBER; change getNames to process
sessionIds in fixed-size chunks (e.g., 500) and query with a stable prepared
statement that accepts a single JSON array parameter (e.g., "WHERE session_id IN
(SELECT value FROM json_each(?)) AND provider = ?"), calling
.all(JSON.stringify(chunk), provider) for each chunk and merging results; update
any callers like applyCustomSessionNames to rely on the new getNames behavior so
large batches are handled without cache misses or parameter-count errors.

Comment thread server/index.js
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/index.js (1)

1632-1642: ⚠️ Potential issue | 🟡 Minor

Inconsistent safeSessionId validation in the token-usage endpoint — missing mismatch check.

The rename endpoint (lines 544–547) correctly rejects when the sanitized sessionId differs from the original. The token-usage endpoint at line 1639 still uses the old pattern — it only guards against an empty result, silently accepting a mutated ID if the original contained characters outside [a-zA-Z0-9._-].

🐛 Proposed fix to align with the rename endpoint
     const safeSessionId = String(sessionId).replace(/[^a-zA-Z0-9._-]/g, '');
-    if (!safeSessionId) {
+    if (!safeSessionId || safeSessionId !== String(sessionId)) {
       return res.status(400).json({ error: 'Invalid sessionId' });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.js` around lines 1632 - 1642, The token-usage route handler
app.get('/api/projects/:projectName/sessions/:sessionId/token-usage',
authenticateToken, ...) currently only checks that safeSessionId is non-empty
after sanitizing; change the validation to also reject when the sanitized ID
differs from the original (i.e., if safeSessionId !== String(sessionId)) and
return 400 with an "Invalid sessionId" error, matching the rename endpoint
behavior so mutated IDs are not silently accepted.
🧹 Nitpick comments (1)
server/index.js (1)

541-565: Rename endpoint validation is well-structured; minor: hoist VALID_PROVIDERS to module scope.

The safeSessionId mismatch-rejection (lines 544–547) correctly implements the fix from the previous review. The summary and provider validations are appropriately guarded. The synchronous sessionNamesDb.setName call is correct given better-sqlite3's synchronous API.

VALID_PROVIDERS is an array literal defined inside the handler and re-allocated on every request. Moving it to module scope is a zero-risk improvement.

♻️ Proposed refactor
+const VALID_PROVIDERS = ['claude', 'codex', 'cursor'];
+
 // Rename session endpoint
 app.put('/api/sessions/:sessionId/rename', authenticateToken, async (req, res) => {
     try {
         ...
-        const VALID_PROVIDERS = ['claude', 'codex', 'cursor'];
         if (!provider || !VALID_PROVIDERS.includes(provider)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.js` around lines 541 - 565, Hoist the VALID_PROVIDERS array out
of the request handler to module scope to avoid reallocating it per request:
define const VALID_PROVIDERS = ['claude','codex','cursor'] near the top of the
file and remove the in-handler declaration; keep the handler
app.put('/api/sessions/:sessionId/rename', authenticateToken, async (req, res)
=> { ... }) unchanged except to reference the module-scoped VALID_PROVIDERS when
validating provider before calling sessionNamesDb.setName(safeSessionId,
provider, summary.trim()).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@server/index.js`:
- Around line 1632-1642: The token-usage route handler
app.get('/api/projects/:projectName/sessions/:sessionId/token-usage',
authenticateToken, ...) currently only checks that safeSessionId is non-empty
after sanitizing; change the validation to also reject when the sanitized ID
differs from the original (i.e., if safeSessionId !== String(sessionId)) and
return 400 with an "Invalid sessionId" error, matching the rename endpoint
behavior so mutated IDs are not silently accepted.

---

Nitpick comments:
In `@server/index.js`:
- Around line 541-565: Hoist the VALID_PROVIDERS array out of the request
handler to module scope to avoid reallocating it per request: define const
VALID_PROVIDERS = ['claude','codex','cursor'] near the top of the file and
remove the in-handler declaration; keep the handler
app.put('/api/sessions/:sessionId/rename', authenticateToken, async (req, res)
=> { ... }) unchanged except to reference the module-scoped VALID_PROVIDERS when
validating provider before calling sessionNamesDb.setName(safeSessionId,
provider, summary.trim()).

@blackmammoth blackmammoth self-requested a review February 23, 2026 05:18
@blackmammoth
Copy link
Copy Markdown
Collaborator

blackmammoth commented Feb 26, 2026

@PaloSP I don't think this works with Codex and Cursor sessions. Can you update it?

@blackmammoth blackmammoth marked this pull request as draft February 26, 2026 11:40
PaloSP and others added 4 commits March 1, 2026 19:56
fixes siteboon#358)

- Add session_names table to store custom display names per provider
- Add PUT /api/sessions/:sessionId/rename endpoint
- Replace stub updateSessionSummary with real API call
- Apply custom names across all providers (Claude, Codex, Cursor)
- Fix project rename destroying config (spread merge instead of overwrite)
- Thread provider parameter through sidebar component chain
- Add i18n error messages for rename failures (en, ja, ko, zh-CN)
- Log migration errors instead of swallowing them silently (db.js)
- Add try/catch to applyCustomSessionNames to prevent getProjects abort
- Move applyCustomSessionNames to db.js as shared helper (DRY)
- Fix Cursor getSessionName to check session.summary for custom names
- Move edit state clearing to finally block in updateSessionSummary
- Sanitize sessionId, add 500-char summary limit, validate provider whitelist
- Remove dead applyCustomSessionNames call on empty manual project sessions
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…phans

- Enable rename UI (pencil icon) for Codex, Cursor, and Gemini sessions
- Keep delete button hidden for Cursor (no backend delete endpoint)
- Add 'gemini' to VALID_PROVIDERS and hoist to module scope
- Add sessionNamesDb.deleteName on session delete (claude, codex, gemini)
- Fix token-usage endpoint sessionId mismatch validation
- Remove redundant try/catch in sessionNamesDb methods
- Let session_names migration errors propagate to outer handler
@PaloSP PaloSP force-pushed the fix/session-rename branch from 3cdd067 to 75198e8 Compare March 1, 2026 19:26
@PaloSP
Copy link
Copy Markdown
Contributor Author

PaloSP commented Mar 1, 2026

@blackmammoth Fixed — rename now works for all providers (Claude, Codex, Cursor, Gemini).

The issue was that the edit/delete hover buttons in SidebarSessionItem.tsx were hidden for Codex and Cursor sessions. Removed those guards so the pencil icon shows for all providers. The backend already supported all providers. Also added Gemini support and custom session names are now cleaned up when a session is deleted.

Tested with Claude and Codex. Don't have Cursor/Gemini set up to test locally.

Copy link
Copy Markdown
Collaborator

@blackmammoth blackmammoth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested and works well.

@blackmammoth blackmammoth marked this pull request as ready for review March 3, 2026 15:11
@blackmammoth blackmammoth merged commit 198e3da into siteboon:main Mar 3, 2026
@blackmammoth
Copy link
Copy Markdown
Collaborator

@PaloSP I see. It's all working now. I have also tested it with Gemini and it works. Merged!

@blackmammoth
Copy link
Copy Markdown
Collaborator

blackmammoth commented Mar 3, 2026

Hey @PaloSP, if you’d like to follow project discussions or hang out with other contributors, we also have a Discord here: link. Totally optional!

SuperOuxx pushed a commit to SuperOuxx/coding-agent-ui that referenced this pull request Mar 5, 2026
* feat: implement session rename with SQLite storage (closes siteboon#72, fixes siteboon#358)

- Add session_names table to store custom display names per provider
- Add PUT /api/sessions/:sessionId/rename endpoint
- Replace stub updateSessionSummary with real API call
- Apply custom names across all providers (Claude, Codex, Cursor)
- Fix project rename destroying config (spread merge instead of overwrite)
- Thread provider parameter through sidebar component chain
- Add i18n error messages for rename failures (en, ja, ko, zh-CN)

* fix: address CodeRabbit review feedback for session rename

- Log migration errors instead of swallowing them silently (db.js)
- Add try/catch to applyCustomSessionNames to prevent getProjects abort
- Move applyCustomSessionNames to db.js as shared helper (DRY)
- Fix Cursor getSessionName to check session.summary for custom names
- Move edit state clearing to finally block in updateSessionSummary
- Sanitize sessionId, add 500-char summary limit, validate provider whitelist
- Remove dead applyCustomSessionNames call on empty manual project sessions

* fix: reject sessionId on mismatch instead of silent normalization

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix: enable rename for all providers, add Gemini support, clean up orphans

- Enable rename UI (pencil icon) for Codex, Cursor, and Gemini sessions
- Keep delete button hidden for Cursor (no backend delete endpoint)
- Add 'gemini' to VALID_PROVIDERS and hoist to module scope
- Add sessionNamesDb.deleteName on session delete (claude, codex, gemini)
- Fix token-usage endpoint sessionId mismatch validation
- Remove redundant try/catch in sessionNamesDb methods
- Let session_names migration errors propagate to outer handler

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Haileyesus <118998054+blackmammoth@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The project disappears after renaming Update Session Summary not functioning

2 participants