feat: fix AI session auto-renaming title extraction / 修复会话自动重命名标题提取#941
feat: fix AI session auto-renaming title extraction / 修复会话自动重命名标题提取#941WenhuaXia wants to merge 2 commits into
Conversation
📝 WalkthroughWalkthroughAdds Anthropic-backed session title generation and JSONL title parsing, updates Claude SDK streaming to capture Task subagent prompts, and preserves existing project summaries when incoming values are placeholders. ChangesAI Session Title and Streaming Updates
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@server/claude-sdk.js`:
- Around line 59-60: The JSON parsing guard in claude-sdk.js is too loose
because JSON.parse('null') still passes the object check and then reading
parsed.prompt can throw. Update the parsing path around the parsed variable to
explicitly reject null before accessing prompt, then keep the existing prompt
extraction logic so the SDK stream safely returns null instead of crashing.
- Around line 754-759: `sessionsService.normalizeMessage` is being called with
`streamingSubagentPrompts`, but the service and Claude provider signatures still
only accept three arguments, so the prompt filter data is dropped. Update the
`normalizeMessage` method in the sessions service, the provider interface, and
the Claude provider implementation to accept the optional fourth parameter and
forward it through to the normalization logic. Make sure the
`streamingSubagentPrompts` Set is passed all the way through so Task prompt
filtering can run during normalization.
In
`@server/modules/providers/list/claude/claude-session-synchronizer.provider.ts`:
- Around line 393-398: The auto-title logging in the session synchronizer is
exposing user prompt text and generated titles, so update the
`claude-session-synchronizer.provider.ts` flow inside the `if (!sessionName)`
block to stop logging sensitive contents. Keep the `console.info` messages in
place if needed, but remove the `prompt` and `title` payloads from the logs in
the `extractLastPrompt` and `generateAiTitle` path, and only log non-sensitive
context such as the session ID.
- Around line 289-296: The prompt-echo check in
claude-session-synchronizer.provider.ts is too aggressive because p.includes(c)
treats a valid title as invalid when it appears inside the prompt. Update the
matching logic in the title-validation path to only reject obvious prompt echoes
or near-duplicates, and avoid using a plain substring containment test against
the full prompt; keep the existing heuristics in the surrounding comparison
function but make the decision in this block rely on stronger signals than
simple inclusion.
- Around line 232-263: The early decision extraction paths in the title parsing
logic can still return prompt-echoed text because they bypass the prompt guard
used later in the same method. Update the candidate handling in the
`claude-session-synchronizer.provider.ts` title extraction flow so every matched
value from the decision and backtick patterns is checked with `isPromptMatch`
against `userPrompt` before returning. Keep the existing fallback behavior in
the relevant parsing routine, but make the guard apply consistently across all
candidate sources, including the initial `decisionPatterns` and
`backtickPatterns` branches.
- Around line 363-371: The skip logic in the Claude session synchronizer is
treating any custom session name longer than 60 characters as eligible for
regeneration, which can overwrite valid user-defined titles. Update the guard in
the session-name handling flow around the existingSessionName check so it
preserves custom names up to the full limit supported by normalizeSessionName
(60 should not be the cutoff), and keep the prompt-derived-name detection using
extractLastPrompt intact. Ensure the decision in ClaudeSessionSynchronizer
reflects “skip unless the name is prompt-derived,” not “skip only when the name
is short.”
- Around line 458-463: The custom-title handling in
claude-session-synchronizer.provider.ts should stop searching once the latest
title is the placeholder, instead of continuing backward and potentially
returning an older stale title. Update the title-scanning logic around the
custom-title branch in the session sync flow so that when claudeRenamedTitle is
"Untitled Claude Session" it is treated as no title and the search terminates
there, allowing AI generation to proceed rather than falling back to earlier
titles.
- Around line 119-133: The auth handling in resolveAnthropicConfig is collapsing
ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN into a single key, which loses
whether the request should use x-api-key or Authorization: Bearer. Update the
config flow in claude-session-synchronizer.provider.ts so it preserves the
credential kind alongside the value, and make the Anthropic request builder
choose the correct header based on that kind. Keep the env and settings.json
reading logic in place, but ensure the returned config from
resolveAnthropicConfig and the caller that sends the Messages request both use
the credential type explicitly.
In `@src/hooks/useProjectsState.ts`:
- Around line 260-267: The downgrade path in useProjectsState currently restores
only summary when normalizedSession has a degraded title, but name can still be
overwritten by the incoming “Untitled Claude Session”. Update the
isDegradedSummary branch so it preserves both session.summary and session.name
from the existing session before/after spreading normalizedSession, using the
existing session and updated object logic in this hook.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 4e04fa80-a9ec-41a7-8ee3-8f2994fe0139
📒 Files selected for processing (3)
server/claude-sdk.jsserver/modules/providers/list/claude/claude-session-synchronizer.provider.tssrc/hooks/useProjectsState.ts
| if (typeof parsed !== 'object') return null; | ||
| const prompt = typeof parsed.prompt === 'string' ? parsed.prompt : null; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Guard JSON null before reading prompt.
JSON.parse('null') passes the typeof parsed === 'object' check, then parsed.prompt throws and can terminate the SDK stream.
Proposed fix
- if (typeof parsed !== 'object') return null;
+ if (typeof parsed !== 'object' || parsed === null) return null;
const prompt = typeof parsed.prompt === 'string' ? parsed.prompt : null;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (typeof parsed !== 'object') return null; | |
| const prompt = typeof parsed.prompt === 'string' ? parsed.prompt : null; | |
| if (typeof parsed !== 'object' || parsed === null) return null; | |
| const prompt = typeof parsed.prompt === 'string' ? parsed.prompt : null; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/claude-sdk.js` around lines 59 - 60, The JSON parsing guard in
claude-sdk.js is too loose because JSON.parse('null') still passes the object
check and then reading parsed.prompt can throw. Update the parsing path around
the parsed variable to explicitly reject null before accessing prompt, then keep
the existing prompt extraction logic so the SDK stream safely returns null
instead of crashing.
| if (process.env.ANTHROPIC_API_KEY?.trim()) key = process.env.ANTHROPIC_API_KEY.trim(); | ||
| if (process.env.ANTHROPIC_AUTH_TOKEN?.trim()) key = process.env.ANTHROPIC_AUTH_TOKEN.trim(); | ||
| if (process.env.ANTHROPIC_BASE_URL?.trim()) baseUrl = process.env.ANTHROPIC_BASE_URL.trim(); | ||
|
|
||
| // Read from ~/.claude/settings.json env block | ||
| try { | ||
| const settingsPath = path.join(this.claudeHome, 'settings.json'); | ||
| const content = await readFile(settingsPath, 'utf8'); | ||
| const settings: any = JSON.parse(content); | ||
| const env = settings?.env; | ||
| if (typeof env === 'object' && env) { | ||
| if (typeof env.ANTHROPIC_API_KEY === 'string' && env.ANTHROPIC_API_KEY.trim()) key = env.ANTHROPIC_API_KEY.trim(); | ||
| if (typeof env.ANTHROPIC_AUTH_TOKEN === 'string' && env.ANTHROPIC_AUTH_TOKEN.trim()) key = env.ANTHROPIC_AUTH_TOKEN.trim(); | ||
| if (typeof env.ANTHROPIC_BASE_URL === 'string' && env.ANTHROPIC_BASE_URL.trim()) baseUrl = env.ANTHROPIC_BASE_URL.trim(); | ||
| if (typeof env.ANTHROPIC_DEFAULT_HAIKU_MODEL === 'string' && env.ANTHROPIC_DEFAULT_HAIKU_MODEL.trim()) model = env.ANTHROPIC_DEFAULT_HAIKU_MODEL.trim(); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- outline ---'
ast-grep outline server/modules/providers/list/claude/claude-session-synchronizer.provider.ts --view expanded || true
echo '--- relevant lines ---'
nl -ba server/modules/providers/list/claude/claude-session-synchronizer.provider.ts | sed -n '100,190p'
echo '--- search x-api-key / ANTHROPIC_AUTH_TOKEN ---'
rg -n "x-api-key|ANTHROPIC_AUTH_TOKEN|ANTHROPIC_API_KEY|Authorization: \`Bearer|authHeader" server/modules/providers server/modules -g'*.ts' -g'*.js' || trueRepository: siteboon/claudecodeui
Length of output: 1360
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- relevant lines 113-190 ---'
sed -n '113,190p' server/modules/providers/list/claude/claude-session-synchronizer.provider.ts
echo '--- relevant lines 190-250 ---'
sed -n '190,250p' server/modules/providers/list/claude/claude-session-synchronizer.provider.ts
echo '--- other Anthropic-related usages ---'
rg -n "resolveAnthropicConfig|generateAiTitle|x-api-key|ANTHROPIC_API_KEY|ANTHROPIC_AUTH_TOKEN|Authorization: \`Bearer" server -g'*.ts' -g'*.js' || trueRepository: siteboon/claudecodeui
Length of output: 9144
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- claude-auth.provider.ts relevant lines ---'
sed -n '1,160p' server/modules/providers/list/claude/claude-auth.provider.ts
echo '--- opencode-auth.provider.ts relevant lines ---'
sed -n '1,120p' server/modules/providers/list/opencode/opencode-auth.provider.ts
echo '--- search credential handling in claude files ---'
rg -n "ANTHROPIC_AUTH_TOKEN|ANTHROPIC_API_KEY|x-api-key|Authorization: \`Bearer|Bearer \${.*key" server/modules/providers/list/claude -g'*.ts' || trueRepository: siteboon/claudecodeui
Length of output: 8638
🌐 Web query:
Anthropic Messages API authentication headers x-api-key Authorization Bearer official docs ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN
💡 Result:
The Anthropic Messages API supports two primary authentication methods: standard API keys and Workload Identity Federation (WIF) [1][2]. Authentication Headers: - x-api-key: Used for static API keys (typically starting with sk-ant-api...) [1]. This header is the standard way to provide your API key for authentication [2][3]. - Authorization: Bearer : Used for short-lived access tokens obtained through Workload Identity Federation [1][2]. Requirements: All API requests must include one of the two authentication headers listed above [2]. Additionally, all requests must include the following mandatory headers: - anthropic-version: Specifies the API version (e.g., 2023-06-01) [2][3]. - content-type: Must be set to application/json [2][3]. SDK Environment Variables: - ANTHROPIC_API_KEY: The official SDKs (Python, TypeScript, etc.) automatically detect and use this environment variable to populate the x-api-key header [1][2][4]. - ANTHROPIC_AUTH_TOKEN: Used in some SDK implementations (or custom integrations) to populate the Authorization: Bearer header [5][6]. If you are using the official Anthropic SDKs, they manage these headers automatically, so you generally do not need to construct them manually [1][2]. When making direct HTTP requests (e.g., via curl), ensure you provide the x-api-key header along with the required version and content-type headers [3]. Note that while some OAuth-related tokens may have historically supported Bearer authentication, current guidance emphasizes the x-api-key header for standard API key-based access [1][7].
Citations:
- 1: https://platform.claude.com/docs/en/manage-claude/authentication
- 2: https://platform.claude.com/docs/en/api/overview
- 3: https://platform.claude.com/docs/en/build-with-claude/working-with-messages?fcdaa149_sort_date=desc&gad_campaignid=23517065231
- 4: https://docs.aws.amazon.com/claude-platform/latest/userguide/making-requests.html
- 5: https://docs.rs/anthropic-async/latest/anthropic_async/config/struct.AnthropicConfig.html
- 6: https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/_client.py
- 7: Anthropic API rejects OAuth tokens sent via Bearer auth — should use X-Api-Key header earendil-works/pi#2751
Preserve Anthropic auth type in the request. Anthropic Messages expects x-api-key for ANTHROPIC_API_KEY and Authorization: Bearer for ANTHROPIC_AUTH_TOKEN/OAuth, so resolveAnthropicConfig should carry the credential kind instead of collapsing both into key; otherwise API-key-only installs will 401 and always fall back to truncation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/modules/providers/list/claude/claude-session-synchronizer.provider.ts`
around lines 119 - 133, The auth handling in resolveAnthropicConfig is
collapsing ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN into a single key, which
loses whether the request should use x-api-key or Authorization: Bearer. Update
the config flow in claude-session-synchronizer.provider.ts so it preserves the
credential kind alongside the value, and make the Anthropic request builder
choose the correct header based on that kind. Keep the env and settings.json
reading logic in place, but ensure the returned config from
resolveAnthropicConfig and the caller that sends the Messages request both use
the credential type explicitly.
| // Direct substring check | ||
| if (c.length >= 4 && p.length >= 4 && (c.includes(p) || p.includes(c))) return true; | ||
| if (pClean.length >= 4 && (c.includes(pClean) || pClean.includes(c))) return true; | ||
| // One starts with the other | ||
| if (c.length >= 4 && p.length >= 4 && (c.startsWith(p) || p.startsWith(c))) return true; | ||
| // Shared prefix overlap | ||
| if (c.length + p.length > 0 && Math.min(c.length, p.length) / Math.max(c.length, p.length) > 0.5) { | ||
| if (c.includes(p.slice(0, 8)) || p.includes(c.slice(0, 8))) return true; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Don’t reject valid titles just because they’re contained in the prompt.
A good title like Fix login bug is often a prefix/subphrase of Fix login bug in production...; p.includes(c) marks it as prompt echo and forces fallback truncation.
🐛 Proposed fix
// Direct substring check
- if (c.length >= 4 && p.length >= 4 && (c.includes(p) || p.includes(c))) return true;
- if (pClean.length >= 4 && (c.includes(pClean) || pClean.includes(c))) return true;
+ const isNearFullOverlap = (left: string, right: string): boolean => {
+ const shorter = Math.min(left.length, right.length);
+ const longer = Math.max(left.length, right.length);
+ return shorter >= 4 && longer > 0 && shorter / longer >= 0.8 && (left.includes(right) || right.includes(left));
+ };
+ if (isNearFullOverlap(c, p)) return true;
+ if (isNearFullOverlap(c, pClean)) return true;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Direct substring check | |
| if (c.length >= 4 && p.length >= 4 && (c.includes(p) || p.includes(c))) return true; | |
| if (pClean.length >= 4 && (c.includes(pClean) || pClean.includes(c))) return true; | |
| // One starts with the other | |
| if (c.length >= 4 && p.length >= 4 && (c.startsWith(p) || p.startsWith(c))) return true; | |
| // Shared prefix overlap | |
| if (c.length + p.length > 0 && Math.min(c.length, p.length) / Math.max(c.length, p.length) > 0.5) { | |
| if (c.includes(p.slice(0, 8)) || p.includes(c.slice(0, 8))) return true; | |
| // Direct substring check | |
| const isNearFullOverlap = (left: string, right: string): boolean => { | |
| const shorter = Math.min(left.length, right.length); | |
| const longer = Math.max(left.length, right.length); | |
| return shorter >= 4 && longer > 0 && shorter / longer >= 0.8 && (left.includes(right) || right.includes(left)); | |
| }; | |
| if (isNearFullOverlap(c, p)) return true; | |
| if (isNearFullOverlap(c, pClean)) return true; | |
| // One starts with the other | |
| if (c.length >= 4 && p.length >= 4 && (c.startsWith(p) || p.startsWith(c))) return true; | |
| // Shared prefix overlap | |
| if (c.length + p.length > 0 && Math.min(c.length, p.length) / Math.max(c.length, p.length) > 0.5) { | |
| if (c.includes(p.slice(0, 8)) || p.includes(c.slice(0, 8))) return true; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/modules/providers/list/claude/claude-session-synchronizer.provider.ts`
around lines 289 - 296, The prompt-echo check in
claude-session-synchronizer.provider.ts is too aggressive because p.includes(c)
treats a valid title as invalid when it appears inside the prompt. Update the
matching logic in the title-validation path to only reject obvious prompt echoes
or near-duplicates, and avoid using a plain substring containment test against
the full prompt; keep the existing heuristics in the surrounding comparison
function but make the decision in this block rely on stronger signals than
simple inclusion.
| if (existingSessionName && existingSessionName !== 'Untitled Claude Session' && existingSessionName.length <= 60) { | ||
| // Check if the existing name is just the raw prompt or a prefix of it | ||
| const lastPrompt = await this.extractLastPrompt(filePath); | ||
| if (lastPrompt && (existingSessionName === lastPrompt || lastPrompt.startsWith(existingSessionName))) { | ||
| // Existing name is derived from the prompt, not AI generated — regenerate | ||
| shouldSkip = false; | ||
| } else { | ||
| shouldSkip = true; | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Preserve real custom names longer than 60 characters.
Line 363 only skips custom names <= 60, so a user’s valid long custom title can be regenerated/overwritten even though normalizeSessionName supports up to 120 chars.
🐛 Proposed fix
- if (existingSessionName && existingSessionName !== 'Untitled Claude Session' && existingSessionName.length <= 60) {
+ if (existingSessionName && existingSessionName !== 'Untitled Claude Session') {
// Check if the existing name is just the raw prompt or a prefix of it
const lastPrompt = await this.extractLastPrompt(filePath);
- if (lastPrompt && (existingSessionName === lastPrompt || lastPrompt.startsWith(existingSessionName))) {
+ const trimmedPrompt = lastPrompt?.trim();
+ const trimmedExistingName = existingSessionName.trim();
+ if (
+ trimmedPrompt
+ && (
+ trimmedExistingName === trimmedPrompt
+ || (trimmedExistingName.length >= 60 && trimmedPrompt.startsWith(trimmedExistingName))
+ )
+ ) {
// Existing name is derived from the prompt, not AI generated — regenerate
shouldSkip = false;
} else {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (existingSessionName && existingSessionName !== 'Untitled Claude Session' && existingSessionName.length <= 60) { | |
| // Check if the existing name is just the raw prompt or a prefix of it | |
| const lastPrompt = await this.extractLastPrompt(filePath); | |
| if (lastPrompt && (existingSessionName === lastPrompt || lastPrompt.startsWith(existingSessionName))) { | |
| // Existing name is derived from the prompt, not AI generated — regenerate | |
| shouldSkip = false; | |
| } else { | |
| shouldSkip = true; | |
| } | |
| if (existingSessionName && existingSessionName !== 'Untitled Claude Session') { | |
| // Check if the existing name is just the raw prompt or a prefix of it | |
| const lastPrompt = await this.extractLastPrompt(filePath); | |
| const trimmedPrompt = lastPrompt?.trim(); | |
| const trimmedExistingName = existingSessionName.trim(); | |
| if ( | |
| trimmedPrompt | |
| && ( | |
| trimmedExistingName === trimmedPrompt | |
| || (trimmedExistingName.length >= 60 && trimmedPrompt.startsWith(trimmedExistingName)) | |
| ) | |
| ) { | |
| // Existing name is derived from the prompt, not AI generated — regenerate | |
| shouldSkip = false; | |
| } else { | |
| shouldSkip = true; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/modules/providers/list/claude/claude-session-synchronizer.provider.ts`
around lines 363 - 371, The skip logic in the Claude session synchronizer is
treating any custom session name longer than 60 characters as eligible for
regeneration, which can overwrite valid user-defined titles. Update the guard in
the session-name handling flow around the existingSessionName check so it
preserves custom names up to the full limit supported by normalizeSessionName
(60 should not be the cutoff), and keep the prompt-derived-name detection using
extractLastPrompt intact. Ensure the decision in ClaudeSessionSynchronizer
reflects “skip unless the name is prompt-derived,” not “skip only when the name
is short.”
| if (!sessionName) { | ||
| const lastPrompt = await this.extractLastPrompt(filePath); | ||
| if (lastPrompt) { | ||
| console.info(`[AutoTitle] Generating AI title for session ${parsed.sessionId}`, { prompt: lastPrompt.slice(0, 80) }); | ||
| sessionName = await this.generateAiTitle(lastPrompt); | ||
| console.info(`[AutoTitle] Result:`, { title: sessionName, sessionId: parsed.sessionId }); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Remove prompt/title contents from logs.
These logs persist user prompt text and generated titles, which can contain secrets or PII.
🛡️ Proposed fix
const lastPrompt = await this.extractLastPrompt(filePath);
if (lastPrompt) {
- console.info(`[AutoTitle] Generating AI title for session ${parsed.sessionId}`, { prompt: lastPrompt.slice(0, 80) });
+ console.debug(`[AutoTitle] Generating AI title for session ${parsed.sessionId}`);
sessionName = await this.generateAiTitle(lastPrompt);
- console.info(`[AutoTitle] Result:`, { title: sessionName, sessionId: parsed.sessionId });
+ console.debug(`[AutoTitle] Generated AI title`, { sessionId: parsed.sessionId, hasTitle: Boolean(sessionName) });
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!sessionName) { | |
| const lastPrompt = await this.extractLastPrompt(filePath); | |
| if (lastPrompt) { | |
| console.info(`[AutoTitle] Generating AI title for session ${parsed.sessionId}`, { prompt: lastPrompt.slice(0, 80) }); | |
| sessionName = await this.generateAiTitle(lastPrompt); | |
| console.info(`[AutoTitle] Result:`, { title: sessionName, sessionId: parsed.sessionId }); | |
| if (!sessionName) { | |
| const lastPrompt = await this.extractLastPrompt(filePath); | |
| if (lastPrompt) { | |
| console.debug(`[AutoTitle] Generating AI title for session ${parsed.sessionId}`); | |
| sessionName = await this.generateAiTitle(lastPrompt); | |
| console.debug(`[AutoTitle] Generated AI title`, { sessionId: parsed.sessionId, hasTitle: Boolean(sessionName) }); | |
| } |
🧰 Tools
🪛 ast-grep (0.44.0)
[warning] 395-395: Avoid logging sensitive data
Context: console.info([AutoTitle] Generating AI title for session ${parsed.sessionId}, { prompt: lastPrompt.slice(0, 80) })
Note: [CWE-532] Insertion of Sensitive Information into Log File.
(log-sensitive-data-typescript)
[warning] 397-397: Avoid logging sensitive data
Context: console.info([AutoTitle] Result:, { title: sessionName, sessionId: parsed.sessionId })
Note: [CWE-532] Insertion of Sensitive Information into Log File.
(log-sensitive-data-typescript)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/modules/providers/list/claude/claude-session-synchronizer.provider.ts`
around lines 393 - 398, The auto-title logging in the session synchronizer is
exposing user prompt text and generated titles, so update the
`claude-session-synchronizer.provider.ts` flow inside the `if (!sessionName)`
block to stop logging sensitive contents. Keep the `console.info` messages in
place if needed, but remove the `prompt` and `title` payloads from the logs in
the `extractLastPrompt` and `generateAiTitle` path, and only log non-sensitive
context such as the session ID.
Source: Linters/SAST tools
| if (eventType === 'custom-title' && claudeRenamedTitle?.trim()) { | ||
| // Ignore the default "Untitled Claude Session" placeholder — treat it | ||
| // as if there was no title at all so AI generation kicks in. | ||
| if (claudeRenamedTitle.trim() !== 'Untitled Claude Session') { | ||
| return { title: claudeRenamedTitle.trim(), kind: 'custom-title' }; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Stop scanning after the latest placeholder title.
The comment says the placeholder should trigger AI generation, but the loop continues backward and can return an older stale title instead.
🐛 Proposed fix
if (eventType === 'custom-title' && claudeRenamedTitle?.trim()) {
// Ignore the default "Untitled Claude Session" placeholder — treat it
// as if there was no title at all so AI generation kicks in.
- if (claudeRenamedTitle.trim() !== 'Untitled Claude Session') {
- return { title: claudeRenamedTitle.trim(), kind: 'custom-title' };
- }
+ const trimmedTitle = claudeRenamedTitle.trim();
+ if (trimmedTitle === 'Untitled Claude Session') {
+ return undefined;
+ }
+ return { title: trimmedTitle, kind: 'custom-title' };
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (eventType === 'custom-title' && claudeRenamedTitle?.trim()) { | |
| // Ignore the default "Untitled Claude Session" placeholder — treat it | |
| // as if there was no title at all so AI generation kicks in. | |
| if (claudeRenamedTitle.trim() !== 'Untitled Claude Session') { | |
| return { title: claudeRenamedTitle.trim(), kind: 'custom-title' }; | |
| } | |
| if (eventType === 'custom-title' && claudeRenamedTitle?.trim()) { | |
| // Ignore the default "Untitled Claude Session" placeholder — treat it | |
| // as if there was no title at all so AI generation kicks in. | |
| const trimmedTitle = claudeRenamedTitle.trim(); | |
| if (trimmedTitle === 'Untitled Claude Session') { | |
| return undefined; | |
| } | |
| return { title: trimmedTitle, kind: 'custom-title' }; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/modules/providers/list/claude/claude-session-synchronizer.provider.ts`
around lines 458 - 463, The custom-title handling in
claude-session-synchronizer.provider.ts should stop searching once the latest
title is the placeholder, instead of continuing backward and potentially
returning an older stale title. Update the title-scanning logic around the
custom-title branch in the session sync flow so that when claudeRenamedTitle is
"Untitled Claude Session" it is treated as no title and the search terminates
there, allowing AI generation to proceed rather than falling back to earlier
titles.
When the AI model quotes the user's message in its thinking block (e.g. '- **User's message:** ...'), the title extraction mistakenly used the user's own prompt as the session name. Changes: - Add isPromptMatch() to strip markdown prefixes and detect prompt echoes - Add extractTitleFromThinking() for reasoning models (Qwen etc.) - Add truncateToTitle() fallback with Chinese prefix stripping - Lower overlap threshold from 0.7 to 0.5 for prompt matching - Guard text block path against prompt-as-title Fixes: siteboon#926, siteboon#935
Prevent backend 'Untitled Claude Session' or empty titles from overwriting the user-set frontend title in the sidebar.
2e1a3bc to
c3bf430
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@server/modules/providers/list/claude/claude-session-synchronizer.provider.ts`:
- Line 116: The fallback model in claudeSessionSynchronizer is using an outdated
Claude Haiku ID, so update the default in the model selection logic to a current
Claude Haiku 4.5 value. In claude-session-synchronizer.provider.ts, adjust the
let model initialization to use the documented alias or pinned ID
(claude-haiku-4-5 or claude-haiku-4-5-20251001) so /v1/messages resolves to a
valid model.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 0ac08c64-173f-4e8d-8662-594e8f09c8a9
📒 Files selected for processing (3)
server/claude-sdk.jsserver/modules/providers/list/claude/claude-session-synchronizer.provider.tssrc/hooks/useProjectsState.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/hooks/useProjectsState.ts
- server/claude-sdk.js
| private async resolveAnthropicConfig(): Promise<{ key: string; baseUrl: string; model: string } | null> { | ||
| let key: string | null = null; | ||
| let baseUrl = 'https://api.anthropic.com'; | ||
| let model = 'claude-haiku-4-20250915'; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the provider around the default model and its callers/usages.
grep -nR --line-number --context=3 "claude-haiku-4" server/modules/providers/list/claude || true
echo
echo "=== file slice ==="
sed -n '90,150p' server/modules/providers/list/claude/claude-session-synchronizer.provider.ts
echo
echo "=== search for other Claude model defaults ==="
grep -R --line-number --context=2 "claude-haiku-4\|claude-sonnet\|claude-opus" server | head -200 || trueRepository: siteboon/claudecodeui
Length of output: 18683
🌐 Web query:
Anthropic Claude model IDs Haiku 4.5 claude-haiku-4-5-20251001 alias documentation current
💡 Result:
For Claude Haiku 4.5, the specific model ID and alias are as follows: - Claude API ID: claude-haiku-4-5-20251001 [1][2] - Claude API Alias: claude-haiku-4-5 [1][2] In the context of the Claude API, Claude Haiku 4.5 uses a dated model ID (claude-haiku-4-5-20251001) to represent a fixed, pinned snapshot of the model [3][1]. The alias (claude-haiku-4-5) acts as a convenience pointer that resolves to this specific dated version [1]. For other platforms, the identifiers differ to accommodate their specific naming requirements: - Amazon Bedrock: anthropic.claude-haiku-4-5-20251001-v1:0 [4][2] - Google Cloud Vertex AI: claude-haiku-4-5@20251001 [2] Anthropic documentation clarifies that for models released prior to the Claude 4.6 generation, such as Haiku 4.5, aliases serve as pointers to dated snapshots [3][1]. Starting with the 4.6 generation, model IDs transitioned to a dateless format which serves as the canonical, pinned snapshot ID itself [3].
Citations:
- 1: https://platform.claude.com/docs/en/about-claude/models/overview
- 2: https://platform.claude.com/docs/en/about-claude/models/overview.md
- 3: https://platform.claude.com/docs/en/about-claude/models/model-ids-and-versions
- 4: https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-haiku-4-5.html
Use a current Claude Haiku default model. claude-haiku-4-20250915 is not a current Claude Haiku 4.5 ID; the documented alias is claude-haiku-4-5 and the pinned ID is claude-haiku-4-5-20251001. If this fallback is used, /v1/messages will target an unknown model.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/modules/providers/list/claude/claude-session-synchronizer.provider.ts`
at line 116, The fallback model in claudeSessionSynchronizer is using an
outdated Claude Haiku ID, so update the default in the model selection logic to
a current Claude Haiku 4.5 value. In claude-session-synchronizer.provider.ts,
adjust the let model initialization to use the documented alias or pinned ID
(claude-haiku-4-5 or claude-haiku-4-5-20251001) so /v1/messages resolves to a
valid model.
Problem / 问题
When the AI model quotes the user's message in its thinking block (e.g.
- **User's message:** "..."), the session title extraction mistakenly used the user's own prompt as the session name.AI 模型在 thinking 块中引用用户消息时,会话重命名逻辑将用户原话误当作标题。
Solution / 修复
isPromptMatch()strips markdown prefixes (markdown bold, wrapper quotes/backticks) before comparisonextractTitleFromThinking()for reasoning models (Qwen etc.) that produce thinking instead of text blockstruncateToTitle()fallback with Chinese conversational prefix strippingChanged Files
server/modules/providers/list/claude/claude-session-synchronizer.provider.tsSummary by CodeRabbit
New Features
Bug Fixes