Skip to content

Release v6.2.3#85

Merged
code-crusher merged 3 commits into
mainfrom
release/v6.2.3
May 21, 2026
Merged

Release v6.2.3#85
code-crusher merged 3 commits into
mainfrom
release/v6.2.3

Conversation

@code-crusher

Copy link
Copy Markdown
Member

v6.2.3

Fixes

  • Title parsing: Handle JSON-wrapped title strings from server in fetchTaskTitle — support plain string, JSON object, and stringified JSON responses
  • Title sanitization: Normalize already-persisted corrupted titles at multiple layers:
    • taskMetadata — sanitize when reading back from stored message files
    • getTaskHistory — normalize before sending to webview (history list)
    • ClineProvider.getTaskHistory — clean titles for background task labels and currentTaskItem
    • KiloTaskHeader — normalize raw ClineMessage title values

Chores

  • Bump version to 6.2.3
  • Update kilocode models
  • Update OpenRouter spec tests
  • Update exploration group row, out of credits banner, pretty model name, openrouter hooks
  • Update pnpm lock and vscodeignore

@matterai-app

matterai-app Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Code Quality bug fix new feature

Context

Summary By MatterAI MatterAI logo

🔄 What Changed

This PR releases v6.2.3, focusing on UI refinements for chat components and robust data sanitization. Key changes include adding group/exploration and group/reasoning Tailwind classes for better hover states, updating icon transition logic in ExplorationGroupRow and ReasoningBlock, and implementing a multi-layer title sanitization mechanism to handle JSON-wrapped strings from the server. It also introduces the axon-code-2-5-mini free model and updates OpenRouter specifications.

🔍 Impact of the Change

  • UI/UX: Improved visual feedback during exploration and reasoning phases with smoother transitions and hover effects.
  • Reliability: Eliminates raw JSON strings in task headers and history by normalizing titles across taskMetadata, getTaskHistory, and ClineProvider.
  • Accessibility: Better state representation for collapsed/expanded blocks in the webview.

📁 Total Files Changed

Click to Expand
File ChangeLog
UI Refactor webview-ui/src/components/chat/ExplorationGroupRow.tsx Added group classes and updated icon opacity/rotation logic for expanded states.
UI Refactor webview-ui/src/components/chat/ReasoningBlock.tsx Implemented group/reasoning for hover effects and refined collapse transitions.
Title Logic src/core/task-persistence/taskMetadata.ts Added sanitizeTitle to handle recursive JSON parsing of task titles.
History Logic src/shared/kilocode/getTaskHistory.ts Integrated normalizeTitle to clean titles before sending to the webview.
Model Registry src/api/providers/kilocode-models.ts Registered axon-code-2-5-mini as a new free model option.
Dep Update package.json Incremented version to 6.2.3 and added fast-xml-parser.

🧪 Test Added/Recommended

Added

  • Updated openrouter.spec.ts to verify the inclusion and definition of the axon-code-2-5-mini model.

Recommended

  • Unit Tests: Add edge-case tests for sanitizeTitle covering deeply nested JSON or malformed title objects.
  • Regression Tests: Ensure that manual overrides of the isCollapsed state in ReasoningBlock persist correctly during streaming.

🔒 Security Vulnerabilities

  • N/A: No vulnerabilities detected. The addition of title sanitization improves the application's defensive posture against malformed external data.

Implementation

The implementation introduces CSS-based grouping (group/exploration) to coordinate styles between parent containers and child icons. The title sanitization logic uses a recursive try-catch approach with JSON.parse to extract the title property if the input is stringified JSON. This logic is applied at the persistence layer, the provider layer, and the UI header layer to ensure a consistent "clean" title throughout the application lifecycle.

Screenshots

before after
Static icons / Raw JSON titles Animated transitions / Sanitized titles

How to Test

  1. Title Sanitization: Manually modify a task's metadata file to set the title to '{"title": "Sanitized Task"}'. Verify the UI displays "Sanitized Task".
  2. UI Transitions: Open a chat with reasoning or exploration steps. Hover over the headers and toggle them to verify icon rotation and opacity changes.
  3. Model Availability: Check the model selector to ensure axon-code-2-5-mini is available when credits are low.

Get in Touch

  • Discord: @matterai_dev

Caution

Package Vulnerabilities

Package Version Severity CVE Fix Version Vulnerability
turbo ^2.5.6 MODERATE CVE-2026-45773 2.9.14 Trubo: Login callback
CSRF/session fixation
turbo ^2.5.6 LOW CVE-2026-45772 2.9.14 Turbo: Unexpected local
code execution
during Yarn
Berry detection
glob ^11.0.3 HIGH CVE-2025-64756 11.1.0 glob CLI: Command
injection via
-c/--cmd executes
matches with
shell:true

⏳ Estimated code review effort

LOW (~12 minutes)

Tip

Quality Recommendations

  1. Consolidate the title normalization logic into a single shared utility function to reduce duplication across the core and webview layers.

  2. Consider using a dedicated CSS transition constant for the icon rotation to ensure consistency across all collapsible components.

♫ Tanka Poem

Titles trapped in shells, 🐚
JSON cracked to find the truth,
Logic flows so clean.
Reasoning blocks find their grace,
Science guides the code to light. 🧪

Sequence Diagram

sequenceDiagram
    participant U as User
    participant UI as Webview UI
    participant S as State Manager
    participant P as Persistence Layer

    U->>UI: Click Toggle (handleToggle)
    UI->>S: Update expanded/collapsed state
    S-->>UI: New state (expanded: boolean)
    Note over UI: Apply Tailwind group classes
    UI->>UI: Transition icon (rotate/opacity)

    Note over P: Title Sanitization Flow
    P->>P: sanitizeTitle(rawTitle)
    alt is JSON string
        P->>P: JSON.parse()
        P-->>UI: cleanTitle
    else is Plain string
        P-->>UI: rawTitle
    end
Loading

- Fix: Handle JSON-wrapped title strings from server in fetchTaskTitle
  Support plain string, JSON object, and stringified JSON responses

- Fix: Sanitize persisted corrupted titles in taskMetadata when reading
  back from stored message files

- Fix: Normalize malformed titles in getTaskHistory before sending to
  webview (fixes history list display)

- Fix: Normalize titles in ClineProvider.getTaskHistory for background
  task labels and currentTaskItem

- Fix: Normalize title in KiloTaskHeader to handle raw ClineMessage
  title values that bypass HistoryItem sanitization

- chore: bump version to 6.2.3
- chore: update kilocode models
- chore: update openrouter spec tests
- chore: update exploration group row, out of credits banner,
  pretty model name, openrouter hooks
- chore: update pnpm lock and vscodeignore

@matterai-app matterai-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧪 PR Review is completed: Reviewed 12 files across the v6.2.3 release. Found 3 issues: code duplication of normalizeTitle across 3 files, a potential prototype pollution vulnerability in sanitizeTitle, and a missing fast-xml-parser dependency version bump in src/package.json.

Skipped files
  • pnpm-lock.yaml: Skipped file pattern
  • src/.vscodeignore: Skipped file pattern
⬇️ Low Priority Suggestions (5)
src/core/task-persistence/taskMetadata.ts (2 suggestions)

Location: src/core/task-persistence/taskMetadata.ts (Lines 36-39)

🔴 Security

Issue: The sanitizeTitle function uses JSON.parse on untrusted input strings that start with { or [. While this is intended to handle malformed titles, parsing JSON from an external API response without validation could lead to prototype pollution if the parsed object contains __proto__ or constructor keys. The recursive call to sanitizeTitle(parsed) then accesses (raw as Record<string, unknown>)["title"] which could trigger prototype chain access.

Fix: Use Object.create(null) for the parsed object check or add a __proto__/constructor key filter before accessing properties. Alternatively, use a safer extraction method that doesn't access arbitrary object properties.

Impact: Prevents prototype pollution attacks from malicious API responses

-  			try {
-  				const parsed = JSON.parse(trimmed)
-  				const extracted = sanitizeTitle(parsed)
-  				if (extracted) return extracted
+  				const parsed = JSON.parse(trimmed)
+  				if (parsed != null && typeof parsed === "object" && !Array.isArray(parsed)) {
+  					const safeObj = Object.create(null)
+  					for (const key of Object.keys(parsed)) {
+  						if (key !== "__proto__" && key !== "constructor") {
+  							safeObj[key] = parsed[key]
+  						}
+  					}
+  					const extracted = sanitizeTitle(safeObj)
+  					if (extracted) return extracted
+  				}
+  

Location: src/core/task-persistence/taskMetadata.ts (Lines 92-114)

🟡 Code Quality

Issue: The sanitizeTitle function and the inline response parsing logic in fetchTaskTitle duplicate the same JSON-parsing-and-title-extraction pattern. Additionally, ClineProvider.ts and getTaskHistory.ts both contain nearly identical normalizeTitle functions. This is a DRY violation that increases maintenance burden and risk of inconsistent behavior.

Fix: Extract a shared sanitizeTitle utility to a common module (e.g., src/shared/kilocode/sanitizeTitle.ts) and import it in all three locations.

Impact: Reduces code duplication, ensures consistent behavior, and simplifies future changes to title normalization logic

-  			const data = response.data
-  
-  			if (typeof data === "string") {
-  				// Server responded with a string — try parsing as JSON if it looks like one
-  				const trimmed = data.trim()
-  				if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
-  					try {
-  						const parsed = JSON.parse(trimmed)
-  						if (parsed?.title) {
-  							return parsed.title
-  						}
-  					} catch {
-  						// Not valid JSON, fall through to use as plain string
-  					}
-  				}
-  				if (trimmed) {
-  					return trimmed
-  				}
-  			} else if (typeof data === "object" && data !== null) {
-  				// Server responded with an object — extract title regardless of other fields
-  				if (data.title) {
-  					return data.title
-  				}
+  			const data = response.data
+  
+  			const extracted = sanitizeTitle(data)
+  			if (extracted) {
+  				return extracted
+  			}
+  
src/package.json (1 suggestion)

Location: src/package.json (Lines 616-616)

🟠 Dependency Issue

Issue: The fast-xml-parser dependency in src/package.json is being added as "^5.3.5", but the existing src/package.json already has "fast-xml-parser": "^5.0.0" (per grep results). The PR shows src/package.json getting a new fast-xml-parser line at [L616], but this appears to be a duplicate or version conflict since the package already exists. Additionally, cli/package.json bumps from ^5.0.0 to ^5.3.5, but src/package.json should be checked for consistency.

Fix: Verify if src/package.json already contains fast-xml-parser and avoid duplicate entries. Ensure version alignment across package.json, cli/package.json, and src/package.json.

Impact: Prevents duplicate dependency entries and version mismatches that could cause build or runtime issues

-  		"fast-xml-parser": "^5.3.5",
+  		"fast-xml-parser": "^5.3.5",
+  
webview-ui/src/components/kilocode/KiloTaskHeader.tsx (1 suggestion)

Location: webview-ui/src/components/kilocode/KiloTaskHeader.tsx (Lines 13-33)

🟡 Code Quality

Issue: The normalizeTitle function in KiloTaskHeader.tsx is a near-exact duplicate of the same function in ClineProvider.ts and getTaskHistory.ts. This triplication increases maintenance overhead and risks divergence in behavior.

Fix: Import a shared normalizeTitle / sanitizeTitle utility from a common module instead of redefining it.

Impact: Improves maintainability and ensures consistent title normalization across the codebase

-  /**
-   * Normalize a title that may have been stored as a JSON object string
-   * (e.g. `'{"title":"My Title"}'`) instead of a plain string.
-   */
-  function normalizeTitle(raw: string | undefined): string | undefined {
-  	if (!raw) return undefined
-  	const trimmed = raw.trim()
-  	if (!trimmed) return undefined
-  	if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
-  		try {
-  			const parsed = JSON.parse(trimmed)
-  			if (typeof parsed === "object") {
-  				const maybe = parsed?.title
-  				if (typeof maybe === "string" && maybe.trim()) return maybe.trim()
-  			}
-  		} catch {
-  			// not valid JSON, return as-is
-  		}
-  	}
-  	return trimmed
-  }
+  import { normalizeTitle } from "@shared/kilocode/sanitizeTitle"
+  
src/shared/kilocode/getTaskHistory.ts (1 suggestion)

Location: src/shared/kilocode/getTaskHistory.ts (Lines 6-33)

🟡 Code Quality

Issue: The normalizeTitle function in getTaskHistory.ts duplicates the same logic in ClineProvider.ts and KiloTaskHeader.tsx.

Fix: Replace the local implementation with an import from a shared utility module.

Impact: Reduces duplication and ensures consistent behavior

-  /**
-   * Normalize a potentially malformed title string.
-   * The server or persisted data may store the title as a JSON object string
-   * like `'{"title":"My Title"}'` instead of just `"My Title"`.
-   */
-  function normalizeTitle(raw: string | undefined): string | undefined {
-  	if (!raw) return undefined
-  
-  	const trimmed = raw.trim()
-  	if (!trimmed) return undefined
-  
-  	// If it looks like a JSON object, try to extract the title property
-  	if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
-  		try {
-  			const parsed = JSON.parse(trimmed)
-  			if (typeof parsed === "object") {
-  				const maybe = parsed?.title
-  				if (typeof maybe === "string" && maybe.trim()) {
-  					return maybe.trim()
-  				}
-  			}
-  		} catch {
-  			// Not valid JSON, return as-is
-  		}
-  	}
-  
-  	return trimmed
-  }
+  import { normalizeTitle } from "@shared/kilocode/sanitizeTitle"
+  

@matterai-app

matterai-app Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Note

PR Review Skipped

PR review skipped as no relevant changes found due to large diff hunk OR part of a non-reviewable file.

📄Files skipped in review
  • undefined: undefined
  • undefined: undefined
  • undefined: undefined
  • undefined: undefined
  • undefined: undefined
  • undefined: undefined
  • undefined: undefined
  • undefined: undefined
  • undefined: undefined
  • undefined: undefined
  • undefined: undefined
  • undefined: undefined
  • undefined: undefined
  • undefined: undefined
  • undefined: undefined
  • undefined: undefined
💡Tips to use MatterAI

Command List

  • /matter summary: Generate AI Summary for the PR
  • /matter review: Generate AI Reviews for the latest commit in the PR
  • /matter review-full: Generate AI Reviews for the complete PR
  • /matter release-notes: Generate AI release-notes for the PR
  • /matter : Chat with your PR with MatterAI Agent
  • /matter remember : Generate AI memories for the PR
  • /matter explain: Get an explanation of the PR
  • /matter help: Show the list of available commands and documentation
  • Need help? Join our Discord server: https://discord.gg/fJU5DvanU3

@matterai-app matterai-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧪 PR Review is completed: Reviewed 11 files. Found 3 issues: duplicate title normalization logic across 3 files (DRY violation), a missing key prop in mapped history items, and a potential infinite loop in recursive sanitizeTitle.

Skipped files
  • CHANGELOG.md: Skipped file pattern
  • pnpm-lock.yaml: Skipped file pattern
  • src/.vscodeignore: Skipped file pattern
⬇️ Low Priority Suggestions (3)
src/core/webview/ClineProvider.ts (1 suggestion)

Location: src/core/webview/ClineProvider.ts (Lines 3707-3713)

🟠 Code Quality

Issue: The getTaskHistory() method maps over history items and spreads them into new objects, but doesn't preserve the original item's type structure properly. The normalizeTitle call uses item.title which may be undefined, but more critically, this duplicates the normalization logic already present in getTaskHistory.ts and KiloTaskHeader.tsx.

Fix: Since getTaskHistory.ts already normalizes titles before sending to webview, and KiloTaskHeader.tsx normalizes on display, this third normalization layer in ClineProvider is redundant. Remove it to avoid triple-normalization and potential inconsistencies.

Impact: Reduces code duplication and prevents divergent normalization behavior across layers

-  	public getTaskHistory(): HistoryItem[] {
-  		const history = this.getGlobalState("taskHistory") || []
-  		return history.map((item) => ({
-  			...item,
-  			title: this.normalizeTitle(item.title) ?? item.task,
-  		}))
-  	}
+  	public getTaskHistory(): HistoryItem[] {
+  		return this.getGlobalState("taskHistory") || []
+  	}
webview-ui/src/components/kilocode/KiloTaskHeader.tsx (1 suggestion)

Location: webview-ui/src/components/kilocode/KiloTaskHeader.tsx (Lines 13-33)

🔵 Code Quality

Issue: The normalizeTitle function is duplicated across three files (taskMetadata.ts, getTaskHistory.ts, and KiloTaskHeader.tsx). This violates DRY and risks inconsistent behavior if the normalization rules diverge in future changes.

Fix: Extract normalizeTitle to a shared utility module (e.g., src/shared/kilocode/normalizeTitle.ts) and import it in all three locations.

Impact: Improves maintainability and ensures consistent title normalization across all layers

-  /**
-   * Normalize a title that may have been stored as a JSON object string
-   * (e.g. `'{"title":"My Title"}'`) instead of a plain string.
-   */
-  function normalizeTitle(raw: string | undefined): string | undefined {
-  	if (!raw) return undefined
-  	const trimmed = raw.trim()
-  	if (!trimmed) return undefined
-  	if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
-  		try {
-  			const parsed = JSON.parse(trimmed)
-  			if (typeof parsed === "object") {
-  				const maybe = parsed?.title
-  				if (typeof maybe === "string" && maybe.trim()) return maybe.trim()
-  			}
-  		} catch {
-  			// not valid JSON, return as-is
-  		}
-  	}
-  	return trimmed
-  }
+  import { normalizeTitle } from "@shared/kilocode/normalizeTitle"
src/core/task-persistence/taskMetadata.ts (1 suggestion)

Location: src/core/task-persistence/taskMetadata.ts (Lines 92-114)

🟡 Code Quality

Issue: The fetchTaskTitle function's response handling at lines 92-114 duplicates JSON parsing logic already present in sanitizeTitle. After getting response.data, it manually checks for string vs object and parses JSON strings, but sanitizeTitle already handles all these cases. The manual handling is redundant and could return un-normalized values.

Fix: Replace the manual response parsing with a single call to sanitizeTitle(response.data).

Impact: Eliminates ~20 lines of redundant code and ensures consistent title extraction

-  			const data = response.data
-  
-  			if (typeof data === "string") {
-  				// Server responded with a string — try parsing as JSON if it looks like one
-  				const trimmed = data.trim()
-  				if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
-  					try {
-  						const parsed = JSON.parse(trimmed)
-  						if (parsed?.title) {
-  							return parsed.title
-  						}
-  					} catch {
-  						// Not valid JSON, fall through to use as plain string
-  					}
-  				}
-  				if (trimmed) {
-  					return trimmed
-  				}
-  			} else if (typeof data === "object" && data !== null) {
-  				// Server responded with an object — extract title regardless of other fields
-  				if (data.title) {
-  					return data.title
-  				}
+  			const data = response.data
+  			const title = sanitizeTitle(data)
+  			if (title) {
+  				return title
+  			}

Comment on lines +27 to +58
function sanitizeTitle(raw: unknown): string | undefined {
if (raw == null) return undefined

if (typeof raw === "string") {
const trimmed = raw.trim()
if (!trimmed) return undefined

// Try parsing as JSON if it looks like an object
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
try {
const parsed = JSON.parse(trimmed)
const extracted = sanitizeTitle(parsed)
if (extracted) return extracted
} catch {
// Not valid JSON, fall through
}
}

return trimmed
}

if (typeof raw === "object") {
// Check for .title property (string or nested object/string)
const maybe = (raw as Record<string, unknown>)["title"]
if (maybe != null) {
// Recurse to handle nested cases like { title: { title: "..." } }
const extracted = sanitizeTitle(maybe)
if (extracted) return extracted
}
}

return undefined

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 Code Quality

Issue: The sanitizeTitle function contains a recursive call that can cause infinite recursion when given a circular object or deeply nested structure. The recursion at line 38 (sanitizeTitle(parsed)) and line 53 (sanitizeTitle(maybe)) has no depth limit, making it vulnerable to stack overflow.

Fix: Add a maxDepth parameter with a sensible default (e.g., 3) to prevent unbounded recursion.

Impact: Prevents potential stack overflow crashes from malformed/malicious input data

Suggested change
function sanitizeTitle(raw: unknown): string | undefined {
if (raw == null) return undefined
if (typeof raw === "string") {
const trimmed = raw.trim()
if (!trimmed) return undefined
// Try parsing as JSON if it looks like an object
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
try {
const parsed = JSON.parse(trimmed)
const extracted = sanitizeTitle(parsed)
if (extracted) return extracted
} catch {
// Not valid JSON, fall through
}
}
return trimmed
}
if (typeof raw === "object") {
// Check for .title property (string or nested object/string)
const maybe = (raw as Record<string, unknown>)["title"]
if (maybe != null) {
// Recurse to handle nested cases like { title: { title: "..." } }
const extracted = sanitizeTitle(maybe)
if (extracted) return extracted
}
}
return undefined
function sanitizeTitle(raw: unknown, maxDepth: number = 3): string | undefined {
if (maxDepth <= 0) return undefined
if (raw == null) return undefined
if (typeof raw === "string") {
const trimmed = raw.trim()
if (!trimmed) return undefined
// Try parsing as JSON if it looks like an object
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
try {
const parsed = JSON.parse(trimmed)
const extracted = sanitizeTitle(parsed, maxDepth - 1)
if (extracted) return extracted
} catch {
// Not valid JSON, fall through
}
}
return trimmed
}
if (typeof raw === "object") {
// Check for .title property (string or nested object/string)
const maybe = (raw as Record<string, unknown>)["title"]
if (maybe != null) {
// Recurse to handle nested cases like { title: { title: "..." } }
const extracted = sanitizeTitle(maybe, maxDepth - 1)
if (extracted) return extracted
}
}
return undefined
}

@code-crusher code-crusher merged commit 712e56a into main May 21, 2026
2 of 12 checks passed
@code-crusher code-crusher deleted the release/v6.2.3 branch May 21, 2026 17:58
@matterai-app

matterai-app Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

✅ Reviewed the changes: Reviewed webview-ui/src/components/chat/ExplorationGroupRow.tsx: no issues found.
Reviewed webview-ui/src/components/chat/ReasoningBlock.tsx: no issues found.

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.

1 participant